[flang-commits] [flang] [llvm] [mlir] [flang][OpenMP] Support for "atomic compare capture" (PR #202315)
via flang-commits
flang-commits at lists.llvm.org
Tue Jul 21 00:06:47 PDT 2026
https://github.com/SunilKuravinakop updated https://github.com/llvm/llvm-project/pull/202315
>From 216cbd2e710ce3c725bb51c5606bea4ba84a33a3 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Sat, 6 Jun 2026 14:38:54 -0500
Subject: [PATCH 01/11] Support for "atomic compare capture".
---
flang/lib/Lower/OpenMP/Atomic.cpp | 88 ++++---
.../Interfaces/AtomicInterfaces.td | 9 +-
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 10 +
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 6 +
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 216 ++++++++++++++----
5 files changed, 250 insertions(+), 79 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 9d711b92bb520..5e02d3fb10dbb 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -558,16 +558,45 @@ void Fortran::lower::omp::lowerAtomic(
int action1 = analysis.op1.what & analysis.Action;
memOrder = makeValidForAction(memOrder, action0, action1, version);
+ // --- Shared capture scaffolding ---
+ mlir::Operation *captureOp = nullptr;
+ fir::FirOpBuilder::InsertPoint preAt = builder.saveInsertionPoint();
+ fir::FirOpBuilder::InsertPoint atomicAt, postAt;
+
+ if (construct.IsCapture()) {
+ assert(action0 != analysis.None && action1 != analysis.None &&
+ "Expexcing two actions");
+ (void)action0;
+ (void)action1;
+ captureOp = mlir::omp::AtomicCaptureOp::create(
+ builder, loc, hint, makeMemOrderAttr(converter, memOrder));
+ // Set the non-atomic insertion point to before the atomic.capture.
+ preAt = getInsertionPointBefore(captureOp);
+
+ mlir::Block *block = builder.createBlock(&captureOp->getRegion(0));
+ builder.setInsertionPointToEnd(block);
+ // Set the atomic insertion point to before the terminator inside
+ // atomic.capture.
+ mlir::Operation *term = mlir::omp::TerminatorOp::create(builder, loc);
+ atomicAt = getInsertionPointBefore(term);
+ postAt = getInsertionPointAfter(captureOp);
+ hint = nullptr;
+ memOrder = std::nullopt;
+ }
+
if (auto *cond = get(analysis.cond)) {
// atomic compare: if (x == e) x = d
// e : expecteVal
// d : desiredVal
- // Check for compound clauses (fail, capture, weak) that are not yet
+ // Restore insertion point so pre-processing code (e.g. computing
+ // expectedVal) is emitted before the capture op, not after the terminator.
+ builder.restoreInsertionPoint(preAt);
+
+ // Check for compound clauses (fail, weak) that are not yet
// supported with atomic compare.
if (llvm::any_of(clauses, [](const omp::Clause &clause) {
return clause.id == llvm::omp::Clause::OMPC_fail ||
- clause.id == llvm::omp::Clause::OMPC_capture ||
clause.id == llvm::omp::Clause::OMPC_weak;
})) {
TODO(loc, "Compound clauses of OpenMP ATOMIC COMPARE");
@@ -616,6 +645,17 @@ void Fortran::lower::omp::lowerAtomic(
expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
}
+ // If this is a compare+capture, generate the read op first.
+ if (construct.IsCapture()) {
+ assert(get(analysis.op0.assign) && (analysis.op0.what & analysis.Read) &&
+ "Expected a read operation for compare capture");
+ mlir::Operation *readOp = genAtomicRead(
+ converter, semaCtx, loc, stmtCtx, atomAddr, atom,
+ *get(analysis.op0.assign), hint, memOrder, preAt, atomicAt, postAt);
+ assert(readOp && "Should have created an atomic read operation");
+ builder.setInsertionPointAfter(readOp);
+ }
+
mlir::UnitAttr weakAttr = nullptr;
mlir::Operation *atomicOp = mlir::omp::AtomicCompareOp::create(
builder, loc, atomAddr, weakAttr, hint,
@@ -679,34 +719,9 @@ void Fortran::lower::omp::lowerAtomic(
// Generate omp.yield
mlir::omp::YieldOp::create(builder, loc, newVal);
builder.setInsertionPointAfter(atomicOp);
-
// END omp atomic compare
} else {
- mlir::Operation *captureOp = nullptr;
- fir::FirOpBuilder::InsertPoint preAt = builder.saveInsertionPoint();
- fir::FirOpBuilder::InsertPoint atomicAt, postAt;
-
- if (construct.IsCapture()) {
- // Capturing operation.
- assert(action0 != analysis.None && action1 != analysis.None &&
- "Expexcing two actions");
- (void)action0;
- (void)action1;
- captureOp = mlir::omp::AtomicCaptureOp::create(
- builder, loc, hint, makeMemOrderAttr(converter, memOrder));
- // Set the non-atomic insertion point to before the atomic.capture.
- preAt = getInsertionPointBefore(captureOp);
-
- mlir::Block *block = builder.createBlock(&captureOp->getRegion(0));
- builder.setInsertionPointToEnd(block);
- // Set the atomic insertion point to before the terminator inside
- // atomic.capture.
- mlir::Operation *term = mlir::omp::TerminatorOp::create(builder, loc);
- atomicAt = getInsertionPointBefore(term);
- postAt = getInsertionPointAfter(captureOp);
- hint = nullptr;
- memOrder = std::nullopt;
- } else {
+ if (!construct.IsCapture()) {
// Non-capturing operation.
assert(action0 != analysis.None && action1 == analysis.None &&
"Expexcing single action");
@@ -729,16 +744,13 @@ void Fortran::lower::omp::lowerAtomic(
*get(analysis.op1.assign), hint, memOrder, preAt, atomicAt, postAt);
}
- if (construct.IsCapture()) {
- // If this is a capture operation, the first/second ops will be inside
- // of it. Set the insertion point to past the capture op itself.
- builder.restoreInsertionPoint(postAt);
- } else {
- if (secondOp) {
- builder.setInsertionPointAfter(secondOp);
- } else {
- builder.setInsertionPointAfter(firstOp);
- }
+ if (!construct.IsCapture()) {
+ builder.setInsertionPointAfter(secondOp ? secondOp : firstOp);
}
}
+
+ // Shared capture cleanup.
+ if (construct.IsCapture()) {
+ builder.restoreInsertionPoint(postAt);
+ }
}
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index abb21705b3c1c..8c9015f05bb72 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -289,10 +289,12 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
auto secondReadStmt = dyn_cast<AtomicReadOpInterface>(secondOp);
auto secondUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(secondOp);
auto secondWriteStmt = dyn_cast<AtomicWriteOpInterface>(secondOp);
+ auto secondCompareStmt = dyn_cast<AtomicCompareOpInterface>(secondOp);
if (!((firstUpdateStmt && secondReadStmt) ||
(firstReadStmt && secondUpdateStmt) ||
- (firstReadStmt && secondWriteStmt)))
+ (firstReadStmt && secondWriteStmt) ||
+ (firstReadStmt && secondCompareStmt)))
return ops.front().emitError()
<< "invalid sequence of operations in the capture region";
if (firstUpdateStmt && secondReadStmt &&
@@ -310,6 +312,11 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
return firstReadStmt.emitError()
<< "captured variable in atomic.read must be updated in "
"second operation";
+ if (firstReadStmt && secondCompareStmt &&
+ firstReadStmt.getX() != secondCompareStmt.getX())
+ return firstReadStmt.emitError()
+ << "captured variable in atomic.read must be updated in "
+ "second operation";
return mlir::success();
}]
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 0962b330e2f23..1241abc10298f 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -1928,6 +1928,12 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
omp.atomic.write ...
omp.terminator
}
+
+ omp.atomic.capture {
+ omp.atomic.read ...
+ omp.atomic.compare ...
+ omp.terminator
+ }
```
}] # clausesDescription;
@@ -1946,6 +1952,10 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
/// Returns the `atomic.update` operation inside the region, if any.
/// Otherwise, it returns nullptr.
AtomicUpdateOp getAtomicUpdateOp();
+
+ /// Returns the `atomic.compare` operation inside the region, if any.
+ /// Otherwise, it returns nullptr.
+ AtomicCompareOp getAtomicCompareOp();
}] # clausesExtraClassDeclaration;
let hasRegionVerifier = 1;
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index db5fd8f2e3230..0eafd0a267b97 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4615,6 +4615,12 @@ AtomicUpdateOp AtomicCaptureOp::getAtomicUpdateOp() {
return dyn_cast<AtomicUpdateOp>(getSecondOp());
}
+AtomicCompareOp AtomicCaptureOp::getAtomicCompareOp() {
+ if (auto op = dyn_cast<AtomicCompareOp>(getFirstOp()))
+ return op;
+ return dyn_cast<AtomicCompareOp>(getSecondOp());
+}
+
LogicalResult AtomicCaptureOp::verify() {
return verifySynchronizationHint(*this, getHint());
}
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 07fa92bdabe50..4928b288adb65 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4795,6 +4795,43 @@ convertOmpAtomicUpdate(omp::AtomicUpdateOp &opInst,
return success();
}
+/// Helper to extract the OMPAtomicCompareOp from an integer comparison
+/// predicate. Returns std::nullopt for unsupported predicates.
+static std::optional<llvm::omp::OMPAtomicCompareOp>
+convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
+ switch (predicate) {
+ case LLVM::ICmpPredicate::eq:
+ return llvm::omp::OMPAtomicCompareOp::EQ;
+ case LLVM::ICmpPredicate::slt:
+ case LLVM::ICmpPredicate::ult:
+ return llvm::omp::OMPAtomicCompareOp::MIN;
+ case LLVM::ICmpPredicate::sgt:
+ case LLVM::ICmpPredicate::ugt:
+ return llvm::omp::OMPAtomicCompareOp::MAX;
+ default:
+ return std::nullopt;
+ }
+}
+
+/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
+/// predicate. Returns std::nullopt for unsupported predicates.
+static std::optional<llvm::omp::OMPAtomicCompareOp>
+convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
+ switch (predicate) {
+ case LLVM::FCmpPredicate::oeq:
+ case LLVM::FCmpPredicate::ueq:
+ return llvm::omp::OMPAtomicCompareOp::EQ;
+ case LLVM::FCmpPredicate::olt:
+ case LLVM::FCmpPredicate::ult:
+ return llvm::omp::OMPAtomicCompareOp::MIN;
+ case LLVM::FCmpPredicate::ogt:
+ case LLVM::FCmpPredicate::ugt:
+ return llvm::omp::OMPAtomicCompareOp::MAX;
+ default:
+ return std::nullopt;
+ }
+}
+
static LogicalResult
convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvm::IRBuilderBase &builder,
@@ -4803,13 +4840,149 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
if (failed(checkImplementationStatus(*atomicCaptureOp)))
return failure();
+ omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
+ omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
+ omp::AtomicCompareOp atomicCompareOp = atomicCaptureOp.getAtomicCompareOp();
+
+ // If the capture contains an atomic.compare, delegate to
+ // createAtomicCompare with the capture variable (V) set.
+ if (atomicCompareOp) {
+ omp::AtomicReadOp atomicReadOp = atomicCaptureOp.getAtomicReadOp();
+ assert(atomicReadOp && "expected atomic.read in capture+compare");
+
+ Region ®ion = atomicCompareOp.getRegion();
+ Block &block = region.front();
+
+ llvm::Type *llvmXElementType =
+ moduleTranslation.convertType(block.getArgument(0).getType());
+ llvm::Value *llvmX = moduleTranslation.lookupValue(atomicCompareOp.getX());
+ llvm::Value *llvmV = moduleTranslation.lookupValue(atomicReadOp.getV());
+
+ bool isSigned = false;
+ llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicX = {
+ llvmX, llvmXElementType, isSigned, /*IsVolatile=*/false};
+ llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicV = {
+ llvmV, llvmXElementType, /*isSigned=*/false, /*IsVolatile=*/false};
+ llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicR = {nullptr, nullptr, false,
+ false};
+
+ llvm::AtomicOrdering atomicOrdering =
+ convertAtomicOrdering(atomicCaptureOp.getMemoryOrder());
+
+ // Pre-translate non-pattern operations inside the compare region.
+ auto isAtomicComparePatternOp = [](Operation &op) {
+ return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
+ LLVM::OrOp>(op);
+ };
+ for (Operation &op : block.without_terminator()) {
+ if (isAtomicComparePatternOp(op))
+ continue;
+ bool allOperandsMapped =
+ llvm::all_of(op.getOperands(), [&](mlir::Value v) {
+ return moduleTranslation.lookupValue(v) != nullptr;
+ });
+ if (!allOperandsMapped)
+ continue;
+ if (failed(moduleTranslation.convertOperation(op, builder)))
+ return atomicCompareOp.emitError(
+ "failed to translate operation inside atomic compare region");
+ }
+
+ auto materializeValue = [&](mlir::Value val) -> llvm::Value * {
+ if (llvm::Value *existing = moduleTranslation.lookupValue(val))
+ return existing;
+ if (auto loadOp = val.getDefiningOp<LLVM::LoadOp>()) {
+ if (loadOp->getParentRegion() == ®ion) {
+ llvm::Value *loadAddr =
+ moduleTranslation.lookupValue(loadOp.getAddr());
+ if (!loadAddr)
+ return nullptr;
+ llvm::Type *loadType =
+ moduleTranslation.convertType(loadOp.getResult().getType());
+ return builder.CreateLoad(loadType, loadAddr);
+ }
+ }
+ return nullptr;
+ };
+
+ // Extract comparison predicate, eVal, and dVal from the region.
+ llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+ llvm::Value *eVal = nullptr;
+ llvm::Value *dVal = nullptr;
+ bool isXBinopExpr = false;
+
+ for (Operation &op : block.getOperations()) {
+ if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
+ auto maybeOp =
+ convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
+ if (!maybeOp)
+ return atomicCompareOp.emitError(
+ "unsupported comparison predicate in atomic compare");
+ compareOp = *maybeOp;
+
+ LLVM::ICmpPredicate pred = icmpOp.getPredicate();
+ isSigned = (pred == LLVM::ICmpPredicate::slt ||
+ pred == LLVM::ICmpPredicate::sgt ||
+ pred == LLVM::ICmpPredicate::sle ||
+ pred == LLVM::ICmpPredicate::sge);
+
+ isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
+ mlir::Value eOperand =
+ isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
+ eVal = materializeValue(eOperand);
+ } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
+ auto maybeOp =
+ convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
+ if (!maybeOp)
+ return atomicCompareOp.emitError(
+ "unsupported comparison predicate in atomic compare");
+ compareOp = *maybeOp;
+
+ isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
+ mlir::Value eOperand =
+ isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
+ eVal = materializeValue(eOperand);
+ } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+ if (!dVal)
+ dVal = materializeValue(selectOp.getTrueValue());
+ }
+ }
+
+ if (!eVal)
+ return atomicCompareOp.emitError(
+ "failed to extract expected value (e) from atomic compare region");
+ if (!dVal) {
+ auto yieldOp = cast<omp::YieldOp>(block.getTerminator());
+ if (yieldOp.getResults().empty())
+ return atomicCompareOp.emitError(
+ "failed to extract desired value (d) from atomic compare region");
+ dVal = materializeValue(yieldOp.getResults()[0]);
+ }
+
+ llvmAtomicX.IsSigned = isSigned;
+
+ llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+ bool isPostfixUpdate = true;
+
+ bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
+ llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
+ ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+ llvmAtomicR, eVal, dVal, atomicOrdering,
+ compareOp, isXBinopExpr,
+ isPostfixUpdate, /*IsFailOnly=*/false);
+ ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
+
+ if (failed(handleError(afterIP, *atomicCaptureOp)))
+ return failure();
+
+ builder.restoreIP(*afterIP);
+ return success();
+ }
+
mlir::Value mlirExpr;
bool isXBinopExpr = false, isPostfixUpdate = false;
llvm::AtomicRMWInst::BinOp binop = llvm::AtomicRMWInst::BinOp::BAD_BINOP;
- omp::AtomicUpdateOp atomicUpdateOp = atomicCaptureOp.getAtomicUpdateOp();
- omp::AtomicWriteOp atomicWriteOp = atomicCaptureOp.getAtomicWriteOp();
-
assert((atomicUpdateOp || atomicWriteOp) &&
"internal op must be an atomic.update or atomic.write op");
@@ -4896,43 +5069,6 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
return success();
}
-/// Helper to extract the OMPAtomicCompareOp from an integer comparison
-/// predicate. Returns std::nullopt for unsupported predicates.
-static std::optional<llvm::omp::OMPAtomicCompareOp>
-convertICmpPredicateToAtomicCompareOp(LLVM::ICmpPredicate predicate) {
- switch (predicate) {
- case LLVM::ICmpPredicate::eq:
- return llvm::omp::OMPAtomicCompareOp::EQ;
- case LLVM::ICmpPredicate::slt:
- case LLVM::ICmpPredicate::ult:
- return llvm::omp::OMPAtomicCompareOp::MIN;
- case LLVM::ICmpPredicate::sgt:
- case LLVM::ICmpPredicate::ugt:
- return llvm::omp::OMPAtomicCompareOp::MAX;
- default:
- return std::nullopt;
- }
-}
-
-/// Helper to extract the OMPAtomicCompareOp from a floating-point comparison
-/// predicate. Returns std::nullopt for unsupported predicates.
-static std::optional<llvm::omp::OMPAtomicCompareOp>
-convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
- switch (predicate) {
- case LLVM::FCmpPredicate::oeq:
- case LLVM::FCmpPredicate::ueq:
- return llvm::omp::OMPAtomicCompareOp::EQ;
- case LLVM::FCmpPredicate::olt:
- case LLVM::FCmpPredicate::ult:
- return llvm::omp::OMPAtomicCompareOp::MIN;
- case LLVM::FCmpPredicate::ogt:
- case LLVM::FCmpPredicate::ugt:
- return llvm::omp::OMPAtomicCompareOp::MAX;
- default:
- return std::nullopt;
- }
-}
-
/// Converts an omp.atomic.compare operation to LLVM IR.
///
/// if (x == e) x = d
>From 7d3293944598e8090a64cdec2ac3c791a64b73f1 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Mon, 8 Jun 2026 05:12:22 -0500
Subject: [PATCH 02/11] Adding tests for "atomic compare capture".
---
.../Integration/OpenMP/atomic-compare.f90 | 32 +++++++++++
flang/test/Lower/OpenMP/atomic-compare.f90 | 56 +++++++++++++++++++
flang/test/Parser/OpenMP/atomic-unparse.f90 | 29 ++++++++++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 9 +--
mlir/test/Dialect/OpenMP/ops.mlir | 48 ++++++++++++++++
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 41 ++++++++++++++
6 files changed, 211 insertions(+), 4 deletions(-)
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 249fb0dd8fa64..650f64b80af12 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -260,3 +260,35 @@ subroutine atomic_compare_weak(x, e, d)
if (x == e) x = d
end
+! Integer equality compare+capture: cmpxchg + store old value
+!CHECK-LABEL: define void @atomic_compare_capture_int_eq_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+subroutine atomic_compare_capture_int_eq(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end
+
+! Compare+capture with clause order reversed: capture compare
+!CHECK-LABEL: define void @atomic_capture_compare_int_eq_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+subroutine atomic_capture_compare_int_eq(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic capture compare
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index ac70edbed4e60..752a221aa538d 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -161,3 +161,59 @@ subroutine atomic_compare_int_eq_weak(x, e, d)
!$omp atomic compare weak
if (x .eq. e) x = d
end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_int_eq(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: }
+subroutine atomic_compare_capture_int_eq(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x .eq. e) x = d
+ !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_int_gt(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: } {{.*}}weak{{.*}}
+! CHECK: }
+subroutine atomic_compare_capture_int_gt(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture weak
+ v = x
+ if (x > e) x = d
+ !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index 4f3cf0eac0338..dc0cc1a62f6c2 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -192,6 +192,26 @@ program main
i = j
end if
+!COMPARE CAPTURE
+!$omp atomic compare capture
+ k = i
+ if (i .eq. j) then
+ i = k
+ end if
+!$omp end atomic
+!$omp atomic capture compare
+ k = i
+ if (i .eq. j) then
+ i = k
+ end if
+!$omp end atomic
+!$omp atomic capture compare weak
+ k = i
+ if (i < j) then
+ i = k
+ end if
+!$omp end atomic
+
!ATOMIC
!$omp atomic
i = j
@@ -296,6 +316,15 @@ end program main
!CHECK: !$OMP ATOMIC WEAK COMPARE
!CHECK: !$OMP ATOMIC COMPARE SEQ_CST WEAK
+!COMPARE CAPTURE
+
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC CAPTURE COMPARE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC CAPTURE COMPARE WEAK
+!CHECK: !$OMP END ATOMIC
+
!ATOMIC
!CHECK: !$OMP ATOMIC
!CHECK: !$OMP ATOMIC SEQ_CST
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 17f99a8c86d52..c5a07a7dc6cb2 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4963,13 +4963,14 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
bool isPostfixUpdate = true;
+ bool isWeak = atomicCompareOp.getWeak();
bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
- ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
- llvmAtomicR, eVal, dVal, atomicOrdering,
- compareOp, isXBinopExpr,
- isPostfixUpdate, /*IsFailOnly=*/false);
+ ompBuilder->createAtomicCompare(
+ ompLoc, llvmAtomicX, llvmAtomicV, llvmAtomicR, eVal, dVal,
+ atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
+ /*IsFailOnly=*/false, isWeak);
ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
if (failed(handleError(afterIP, *atomicCaptureOp)))
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index acf6879906e74..289ec60551cb7 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2047,6 +2047,54 @@ func.func @omp_atomic_compare(%x: memref<i32>, %e: i32, %d: i32) {
return
}
+// CHECK-LABEL: omp_atomic_compare_capture
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+ // CHECK: omp.atomic.capture {
+ // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+ // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+ // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+ // CHECK-NEXT: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+ // CHECK-NEXT: %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+ // CHECK-NEXT: omp.yield(%[[SEL]] : i32)
+ // CHECK-NEXT: }
+ // CHECK-NEXT: }
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+ omp.atomic.compare %x : memref<i32> {
+ ^bb0(%xval: i32):
+ %cmp = arith.cmpi eq, %xval, %e : i32
+ %sel = arith.select %cmp, %d, %xval : i32
+ omp.yield(%sel : i32)
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: omp_atomic_compare_capture_weak
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_weak(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+ // CHECK: omp.atomic.capture {
+ // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+ // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+ // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+ // CHECK-NEXT: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[E]] : i32
+ // CHECK-NEXT: %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+ // CHECK-NEXT: omp.yield(%[[SEL]] : i32)
+ // CHECK-NEXT: } {weak}
+ // CHECK-NEXT: }
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+ omp.atomic.compare %x : memref<i32> {
+ ^bb0(%xval: i32):
+ %cmp = arith.cmpi sgt, %xval, %e : i32
+ %sel = arith.select %cmp, %d, %xval : i32
+ omp.yield(%sel : i32)
+ } {weak}
+ }
+ return
+}
+
// CHECK-LABEL: omp_sectionsop
func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
%data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 1890eaa6a3f0b..6ce57b304bfd9 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2795,6 +2795,47 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
// -----
+// CHECK-LABEL: @omp_atomic_compare_capture_int_eq
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+ // Integer equality compare+capture → cmpxchg + extractvalue + store
+ // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+ // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+ // CHECK: store i32 %[[OLD]], ptr %[[V]]
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "eq" %xval, %e : i32
+ %sel = llvm.select %cmp, %d, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ }
+ llvm.return
+}
+
+// -----
+
+// CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+ // Integer equality compare+capture → cmpxchg + extractvalue + store
+ // CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+ // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+ // CHECK: store i32 %[[OLD]], ptr %[[V]]
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "eq" %xval, %e : i32
+ %sel = llvm.select %cmp, %d, %xval : i1, i32
+ omp.yield(%sel : i32)
+ } {weak}
+ }
+ llvm.return
+}
+// -----
+
// CHECK-LABEL: @omp_sections_empty
llvm.func @omp_sections_empty() -> () {
omp.sections {
>From f84987dea68a1dedc6b61bf0c9f5a2e24a0a1b40 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 9 Jun 2026 04:42:48 -0500
Subject: [PATCH 03/11] Minor changes incorporating feedback.
---
flang/lib/Lower/OpenMP/Atomic.cpp | 4 ++--
.../Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index c0f97aada637d..aa497b838e5b6 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -567,7 +567,7 @@ void Fortran::lower::omp::lowerAtomic(
if (construct.IsCapture()) {
assert(action0 != analysis.None && action1 != analysis.None &&
- "Expexcing two actions");
+ "Expecting two actions");
(void)action0;
(void)action1;
captureOp = mlir::omp::AtomicCaptureOp::create(
@@ -730,7 +730,7 @@ void Fortran::lower::omp::lowerAtomic(
if (!construct.IsCapture()) {
// Non-capturing operation.
assert(action0 != analysis.None && action1 == analysis.None &&
- "Expexcing single action");
+ "Expecting single action");
assert(!(analysis.op0.what & analysis.Condition));
postAt = atomicAt = preAt;
}
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index 8c9015f05bb72..dc5b04126b757 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -315,7 +315,7 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
if (firstReadStmt && secondCompareStmt &&
firstReadStmt.getX() != secondCompareStmt.getX())
return firstReadStmt.emitError()
- << "captured variable in atomic.read must be updated in "
+ << "captured variable in atomic.read must be compared/updated in "
"second operation";
return mlir::success();
>From ca24a147920d24c188127dbbe37ca62bcaeab792 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 23 Jun 2026 14:18:17 -0500
Subject: [PATCH 04/11] Handling test cases mentionend in OpenMP spec.
---
flang/lib/Lower/OpenMP/Atomic.cpp | 61 ++++++++++++++++---
llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp | 7 ++-
.../Interfaces/AtomicInterfaces.td | 11 +++-
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 2 +
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 12 ++--
5 files changed, 74 insertions(+), 19 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index aa497b838e5b6..209871abd52b6 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -571,7 +571,8 @@ void Fortran::lower::omp::lowerAtomic(
(void)action0;
(void)action1;
captureOp = mlir::omp::AtomicCaptureOp::create(
- builder, loc, hint, makeMemOrderAttr(converter, memOrder));
+ builder, loc, hint, makeMemOrderAttr(converter, memOrder),
+ /*fail_only=*/nullptr);
// Set the non-atomic insertion point to before the atomic.capture.
preAt = getInsertionPointBefore(captureOp);
@@ -646,15 +647,45 @@ void Fortran::lower::omp::lowerAtomic(
expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
}
- // If this is a compare+capture, generate the read op first.
+ // If this is a compare+capture, determine the ordering of ops.
+ // Pattern 1 (prefix): v = x; if (x == e) x = d → read first
+ // Pattern 2 (postfix): if (x == e) x = d; v = x → compare first
+ // Pattern 3 (fail-only): if (x == e) x = d; else v = x → read on failure
+ bool isPostfixCapture = false;
+ bool isFailOnly = false;
+ const evaluate::Assignment *readAssign = nullptr;
if (construct.IsCapture()) {
- assert(get(analysis.op0.assign) && (analysis.op0.what & analysis.Read) &&
- "Expected a read operation for compare capture");
- mlir::Operation *readOp = genAtomicRead(
- converter, semaCtx, loc, stmtCtx, atomAddr, atom,
- *get(analysis.op0.assign), hint, memOrder, preAt, atomicAt, postAt);
- assert(readOp && "Should have created an atomic read operation");
- builder.setInsertionPointAfter(readOp);
+ // Determine which op is the read and check for fail-only (IfFalse).
+ int readWhat = 0;
+ if (analysis.op0.what & analysis.Read) {
+ readAssign = get(analysis.op0.assign);
+ readWhat = analysis.op0.what;
+ } else if (analysis.op1.what & analysis.Read) {
+ readAssign = get(analysis.op1.assign);
+ readWhat = analysis.op1.what;
+ isPostfixCapture = true;
+ }
+ assert(readAssign && "Expected a read assignment for compare capture");
+
+ // Check if the read is conditioned on comparison failure (else branch).
+ if (readWhat & analysis.IfFalse)
+ isFailOnly = true;
+
+ if (!isPostfixCapture && !isFailOnly) {
+ // Pattern 1 (prefix): read is first, generate it before compare.
+ mlir::Operation *readOp =
+ genAtomicRead(converter, semaCtx, loc, stmtCtx, atomAddr, atom,
+ *readAssign, hint, memOrder, preAt, atomicAt, postAt);
+ assert(readOp && "Should have created an atomic read operation");
+ builder.setInsertionPointAfter(readOp);
+ } else {
+ // Pattern 2 (postfix) or 3 (fail-only): compare first, read after.
+ builder.restoreInsertionPoint(atomicAt);
+ }
+
+ // Set the fail_only attribute on the capture op.
+ if (isFailOnly && captureOp)
+ mlir::cast<mlir::omp::AtomicCaptureOp>(captureOp).setFailOnly(true);
}
mlir::UnitAttr weakAttr = nullptr;
@@ -725,6 +756,18 @@ void Fortran::lower::omp::lowerAtomic(
// Generate omp.yield
mlir::omp::YieldOp::create(builder, loc, newVal);
builder.setInsertionPointAfter(atomicOp);
+
+ // Pattern 2 (postfix) or 3 (fail-only): compare first, read second.
+ // Generate read after compare for postfix or fail-only patterns.
+ if (construct.IsCapture() && (isPostfixCapture || isFailOnly)) {
+ fir::FirOpBuilder::InsertPoint afterCompareAt =
+ builder.saveInsertionPoint();
+ mlir::Operation *readOp = genAtomicRead(
+ converter, semaCtx, loc, stmtCtx, atomAddr, atom, *readAssign, hint,
+ memOrder, preAt, afterCompareAt, postAt);
+ assert(readOp && "Should have created an atomic read operation");
+ builder.setInsertionPointAfter(readOp);
+ }
// END omp atomic compare
} else {
if (!construct.IsCapture()) {
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 92eb7de0d882f..b2ccc05ad6770 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -11321,7 +11321,9 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
assert(OldValue->getType() == V.ElemTy &&
"OldValue and V must be of same type");
if (IsPostfixUpdate) {
- Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+ SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
+ Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
+ Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
} else {
SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
if (IsFailOnly) {
@@ -11377,7 +11379,8 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
assert(OldValue->getType() == V.ElemTy &&
"OldValue and V must be of same type");
if (IsPostfixUpdate) {
- Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
+ Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
+ Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
} else {
if (IsFailOnly) {
BasicBlock *CurBB = Builder.GetInsertBlock();
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index dc5b04126b757..5b1527bcb84ec 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -286,6 +286,7 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
auto &secondOp = *ops.getNextNode(firstOp);
auto firstReadStmt = dyn_cast<AtomicReadOpInterface>(firstOp);
auto firstUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(firstOp);
+ auto firstCompareStmt = dyn_cast<AtomicCompareOpInterface>(firstOp);
auto secondReadStmt = dyn_cast<AtomicReadOpInterface>(secondOp);
auto secondUpdateStmt = dyn_cast<AtomicUpdateOpInterface>(secondOp);
auto secondWriteStmt = dyn_cast<AtomicWriteOpInterface>(secondOp);
@@ -294,7 +295,8 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
if (!((firstUpdateStmt && secondReadStmt) ||
(firstReadStmt && secondUpdateStmt) ||
(firstReadStmt && secondWriteStmt) ||
- (firstReadStmt && secondCompareStmt)))
+ (firstReadStmt && secondCompareStmt) ||
+ (firstCompareStmt && secondReadStmt)))
return ops.front().emitError()
<< "invalid sequence of operations in the capture region";
if (firstUpdateStmt && secondReadStmt &&
@@ -315,8 +317,13 @@ def AtomicCaptureOpInterface : OpInterface<"AtomicCaptureOpInterface"> {
if (firstReadStmt && secondCompareStmt &&
firstReadStmt.getX() != secondCompareStmt.getX())
return firstReadStmt.emitError()
- << "captured variable in atomic.read must be compared/updated in "
+ << "captured variable in atomic.read must be updated in "
"second operation";
+ if (firstCompareStmt && secondReadStmt &&
+ secondReadStmt.getX() != firstCompareStmt.getX())
+ return secondReadStmt.emitError()
+ << "captured variable in atomic.read must be updated in "
+ "first operation";
return mlir::success();
}]
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index ebee887f2afd1..69c22641df6a2 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -1938,6 +1938,8 @@ def AtomicCaptureOp : OpenMP_Op<"atomic.capture", traits = [
```
}] # clausesDescription;
+ let arguments = !con(clausesArgs, (ins UnitAttr:$fail_only));
+
// Override region definition.
let regions = (region SizedRegion<1>:$region);
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index dff1ae30e20aa..76616c7fba27a 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4974,15 +4974,15 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvmAtomicX.IsSigned = isSigned;
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
- bool isPostfixUpdate = true;
- bool isWeak = atomicCompareOp.getWeak();
+ bool isFailOnly = atomicCaptureOp.getFailOnly();
+ bool isPostfixUpdate = !isFailOnly;
bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
- ompBuilder->createAtomicCompare(
- ompLoc, llvmAtomicX, llvmAtomicV, llvmAtomicR, eVal, dVal,
- atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
- /*IsFailOnly=*/false, isWeak);
+ ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+ llvmAtomicR, eVal, dVal, atomicOrdering,
+ compareOp, isXBinopExpr,
+ isPostfixUpdate, isFailOnly);
ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
if (failed(handleError(afterIP, *atomicCaptureOp)))
>From afaf2025463e9e8cf15974747fa9e2d8b8e5fa95 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 23 Jun 2026 18:02:52 -0500
Subject: [PATCH 05/11] Adding tests into flang and mlir for the grammar
mentionend in the spec.
---
.../Integration/OpenMP/atomic-compare.f90 | 56 ++++++++++++++++--
flang/test/Lower/OpenMP/atomic-compare.f90 | 59 +++++++++++++++++++
flang/test/Parser/OpenMP/atomic-unparse.f90 | 17 ++++++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 17 +++++-
mlir/test/Dialect/OpenMP/ops.mlir | 48 +++++++++++++++
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 58 ++++++++++++++++--
6 files changed, 245 insertions(+), 10 deletions(-)
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 650f64b80af12..da86f3ff6ef16 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -260,14 +260,17 @@ subroutine atomic_compare_weak(x, e, d)
if (x == e) x = d
end
-! Integer equality compare+capture: cmpxchg + store old value
+! Integer equality compare+capture (prefix): v=x; if(x==e) x=d
+! v captures old value of x
!CHECK-LABEL: define void @atomic_compare_capture_int_eq_(
!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
subroutine atomic_compare_capture_int_eq(x, e, d, v)
integer :: x, e, d, v
!$omp atomic compare capture
@@ -276,14 +279,16 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
!$omp end atomic
end
-! Compare+capture with clause order reversed: capture compare
+! Compare+capture with clause order reversed: capture compare (still prefix read)
!CHECK-LABEL: define void @atomic_capture_compare_int_eq_(
!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
subroutine atomic_capture_compare_int_eq(x, e, d, v)
integer :: x, e, d, v
!$omp atomic capture compare
@@ -292,3 +297,46 @@ subroutine atomic_capture_compare_int_eq(x, e, d, v)
!$omp end atomic
end
+! Postfix compare+capture: if (x == e) x = d; v = x
+! v captures new value (d if swapped, old x if not)
+!CHECK-LABEL: define void @atomic_compare_capture_postfix_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[DVAL]], i32 %[[OLD]]
+!CHECK: store i32 %[[NEWVAL]], ptr %[[V]]
+subroutine atomic_compare_capture_postfix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! Fail-only compare+capture: if (x == e) x = d; else v = x
+! v is only written when the comparison fails
+!CHECK-LABEL: define void @atomic_compare_capture_fail_only_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]]
+!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+!CHECK: br i1 %[[SUCCESS]], label %[[EXIT:.*]], label %[[CONT:.*]]
+!CHECK: {{.*}}:
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
+!CHECK: br label %[[EXIT2:.*]]
+subroutine atomic_compare_capture_fail_only(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index 752a221aa538d..c770605954539 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -217,3 +217,62 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
if (x > e) x = d
!$omp end atomic
end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_postfix(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine atomic_compare_capture_postfix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eq. e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_fail_only(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: } {fail_only}
+subroutine atomic_compare_capture_fail_only(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eq. e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index dc0cc1a62f6c2..521c1bb2811e2 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -211,6 +211,19 @@ program main
i = k
end if
!$omp end atomic
+!$omp atomic compare capture
+ if (i .eq. j) then
+ i = k
+ end if
+ k = i
+!$omp end atomic
+!$omp atomic compare capture
+ if (i .eq. j) then
+ i = k
+ else
+ k = i
+ end if
+!$omp end atomic
!ATOMIC
!$omp atomic
@@ -324,6 +337,10 @@ end program main
!CHECK: !$OMP END ATOMIC
!CHECK: !$OMP ATOMIC CAPTURE COMPARE WEAK
!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
!ATOMIC
!CHECK: !$OMP ATOMIC
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 76616c7fba27a..b6fc6d6dc3038 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -4974,15 +4974,28 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvmAtomicX.IsSigned = isSigned;
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
+ // In OMPIRBuilder::createAtomicCompare:
+ // IsPostfixUpdate=true → select(success, D, OldValue) → new value
+ // IsPostfixUpdate=false → select(success, E, OldValue) → old value
+ // Prefix read (v=x; if(x==e) x=d): v captures old → IsPostfixUpdate=false
+ // Postfix read (if(x==e) x=d; v=x): v captures new → IsPostfixUpdate=true
+ // Fail-only: conditional store only on failure → IsPostfixUpdate=false
+ bool isPostfixUpdate =
+ !isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
bool isFailOnly = atomicCaptureOp.getFailOnly();
- bool isPostfixUpdate = !isFailOnly;
+ // For fail-only, the OMPIRBuilder's conditional store logic is under
+ // the !IsPostfixUpdate path, so force it to false.
+ if (isFailOnly)
+ isPostfixUpdate = false;
+
+ bool isWeak = atomicCompareOp.getWeak();
bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
llvmAtomicR, eVal, dVal, atomicOrdering,
compareOp, isXBinopExpr,
- isPostfixUpdate, isFailOnly);
+ isPostfixUpdate, isFailOnly, isWeak);
ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
if (failed(handleError(afterIP, *atomicCaptureOp)))
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index 87f9a6f8e4119..6b13be481e0e0 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2113,6 +2113,54 @@ func.func @omp_atomic_compare_capture_weak(%v: memref<i32>, %x: memref<i32>, %e:
return
}
+// CHECK-LABEL: omp_atomic_compare_capture_postfix
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_postfix(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+ // CHECK: omp.atomic.capture {
+ // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+ // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+ // CHECK-NEXT: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+ // CHECK-NEXT: %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+ // CHECK-NEXT: omp.yield(%[[SEL]] : i32)
+ // CHECK-NEXT: }
+ // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+ // CHECK-NEXT: }
+ omp.atomic.capture {
+ omp.atomic.compare %x : memref<i32> {
+ ^bb0(%xval: i32):
+ %cmp = arith.cmpi eq, %xval, %e : i32
+ %sel = arith.select %cmp, %d, %xval : i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+ }
+ return
+}
+
+// CHECK-LABEL: omp_atomic_compare_capture_fail_only
+// CHECK-SAME: (%[[V:.*]]: memref<i32>, %[[X:.*]]: memref<i32>, %[[E:.*]]: i32, %[[D:.*]]: i32)
+func.func @omp_atomic_compare_capture_fail_only(%v: memref<i32>, %x: memref<i32>, %e: i32, %d: i32) {
+ // CHECK: omp.atomic.capture {
+ // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<i32> {
+ // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: i32):
+ // CHECK-NEXT: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[E]] : i32
+ // CHECK-NEXT: %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : i32
+ // CHECK-NEXT: omp.yield(%[[SEL]] : i32)
+ // CHECK-NEXT: }
+ // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<i32>, memref<i32>, i32
+ // CHECK-NEXT: } {fail_only}
+ omp.atomic.capture {
+ omp.atomic.compare %x : memref<i32> {
+ ^bb0(%xval: i32):
+ %cmp = arith.cmpi eq, %xval, %e : i32
+ %sel = arith.select %cmp, %d, %xval : i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : memref<i32>, memref<i32>, i32
+ } {fail_only}
+ return
+}
+
// CHECK-LABEL: omp_sectionsop
func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
%data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 6ce57b304bfd9..978450d598b9b 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2798,10 +2798,12 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
// CHECK-LABEL: @omp_atomic_compare_capture_int_eq
// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
- // Integer equality compare+capture → cmpxchg + extractvalue + store
+ // Integer equality prefix compare+capture → cmpxchg + select(success, E, old) + store
// CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
- // CHECK: store i32 %[[OLD]], ptr %[[V]]
+ // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+ // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
+ // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
omp.atomic.capture {
omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
omp.atomic.compare %x : !llvm.ptr {
@@ -2819,10 +2821,12 @@ llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e
// CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
- // Integer equality compare+capture → cmpxchg + extractvalue + store
+ // Integer equality weak prefix compare+capture → cmpxchg + select(success, E, old) + store
// CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
- // CHECK: store i32 %[[OLD]], ptr %[[V]]
+ // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+ // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
+ // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
omp.atomic.capture {
omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
omp.atomic.compare %x : !llvm.ptr {
@@ -2836,6 +2840,52 @@ llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr
}
// -----
+// CHECK-LABEL: @omp_atomic_compare_capture_postfix
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+ // Postfix compare+capture: v captures new value (d if swapped, old x if not)
+ // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+ // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+ // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+ // CHECK: %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[D]], i32 %[[OLD]]
+ // CHECK: store i32 %[[NEWVAL]], ptr %[[V]]
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "eq" %xval, %e : i32
+ %sel = llvm.select %cmp, %d, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ }
+ llvm.return
+}
+// -----
+
+// CHECK-LABEL: @omp_atomic_compare_capture_fail_only
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
+ // Fail-only compare+capture: v is only written when comparison fails
+ // CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
+ // CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+ // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+ // CHECK: br i1 %[[SUCCESS]], label %[[EXIT:.*]], label %[[CONT:.*]]
+ // CHECK: [[CONT]]:
+ // CHECK: store i32 %[[OLD]], ptr %[[V]]
+ // CHECK: br label %[[EXIT2:.*]]
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "eq" %xval, %e : i32
+ %sel = llvm.select %cmp, %d, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ } {fail_only}
+ llvm.return
+}
+// -----
+
// CHECK-LABEL: @omp_sections_empty
llvm.func @omp_sections_empty() -> () {
omp.sections {
>From e8462a267b6d010ebc1e3ed881c1f38a9faca6be Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 24 Jun 2026 14:44:43 -0500
Subject: [PATCH 06/11] Simplifying the IR generator for one of the cases. This
also fixes the failure in clang for "atomic compare capture".
---
.../Integration/OpenMP/atomic-compare.f90 | 8 +---
llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp | 7 +--
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 43 +++++++++++++------
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 12 ++----
4 files changed, 38 insertions(+), 32 deletions(-)
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index da86f3ff6ef16..4ae1b9a1f1e52 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -268,9 +268,7 @@ subroutine atomic_compare_weak(x, e, d)
!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
-!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
subroutine atomic_compare_capture_int_eq(x, e, d, v)
integer :: x, e, d, v
!$omp atomic compare capture
@@ -286,9 +284,7 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
!CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]]
!CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
-!CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
-!CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[EVAL]], i32 %[[OLD]]
-!CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+!CHECK: store i32 %[[OLD]], ptr %[[V]]
subroutine atomic_capture_compare_int_eq(x, e, d, v)
integer :: x, e, d, v
!$omp atomic capture compare
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 51f210e189e22..f8c1999fe1b89 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -11400,9 +11400,7 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
assert(OldValue->getType() == V.ElemTy &&
"OldValue and V must be of same type");
if (IsPostfixUpdate) {
- SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
- Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
- Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
+ Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
} else {
SuccessOrFail = Builder.CreateExtractValue(Result, /*Idxs=*/1);
if (IsFailOnly) {
@@ -11458,8 +11456,7 @@ OpenMPIRBuilder::InsertPointTy OpenMPIRBuilder::createAtomicCompare(
assert(OldValue->getType() == V.ElemTy &&
"OldValue and V must be of same type");
if (IsPostfixUpdate) {
- Value *NewValue = Builder.CreateSelect(SuccessOrFail, D, OldValue);
- Builder.CreateStore(NewValue, V.Var, V.IsVolatile);
+ Builder.CreateStore(OldValue, V.Var, V.IsVolatile);
} else {
if (IsFailOnly) {
BasicBlock *CurBB = Builder.GetInsertBlock();
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 85389caa9ac42..e1fb08d780753 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5499,25 +5499,23 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvmAtomicX.IsSigned = isSigned;
llvm::OpenMPIRBuilder::LocationDescription ompLoc(builder);
- // In OMPIRBuilder::createAtomicCompare:
- // IsPostfixUpdate=true → select(success, D, OldValue) → new value
- // IsPostfixUpdate=false → select(success, E, OldValue) → old value
- // Prefix read (v=x; if(x==e) x=d): v captures old → IsPostfixUpdate=false
- // Postfix read (if(x==e) x=d; v=x): v captures new → IsPostfixUpdate=true
- // Fail-only: conditional store only on failure → IsPostfixUpdate=false
- bool isPostfixUpdate =
- !isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
+ bool isReadFirst = isa<omp::AtomicReadOp>(atomicCaptureOp.getFirstOp());
+ bool isPostfixCapture = !isReadFirst;
bool isFailOnly = atomicCaptureOp.getFailOnly();
- // For fail-only, the OMPIRBuilder's conditional store logic is under
- // the !IsPostfixUpdate path, so force it to false.
- if (isFailOnly)
- isPostfixUpdate = false;
+ llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
+ // Postfix: bypass V in OMPIRBuilder, handled manually below.
+ if (isPostfixCapture && !isFailOnly)
+ llvmAtomicVForCall = {nullptr, nullptr, false, false};
+
+ // Prefix: IsPostfixUpdate=true → direct store of old value.
+ // Fail-only: IsPostfixUpdate=false → conditional store on failure.
+ bool isPostfixUpdate = !isFailOnly;
bool isWeak = atomicCompareOp.getWeak();
bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
- ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicV,
+ ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicVForCall,
llvmAtomicR, eVal, dVal, atomicOrdering,
compareOp, isXBinopExpr,
isPostfixUpdate, isFailOnly, isWeak);
@@ -5527,6 +5525,25 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
return failure();
builder.restoreIP(*afterIP);
+
+ // Postfix: v = select(success, D, old) — captures new value of x.
+ if (isPostfixCapture && !isFailOnly) {
+ llvm::BasicBlock *curBB = builder.GetInsertBlock();
+ llvm::Instruction *cmpxchgInst = nullptr;
+ for (auto &inst : llvm::reverse(*curBB)) {
+ if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
+ cmpxchgInst = &inst;
+ break;
+ }
+ }
+ assert(cmpxchgInst && "expected cmpxchg instruction");
+ llvm::Value *oldVal = builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/0);
+ llvm::Value *successVal =
+ builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/1);
+ llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
+ builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+ }
+
return success();
}
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 978450d598b9b..377bd88894854 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2798,12 +2798,10 @@ llvm.func @omp_atomic_compare_float_neg_zero(%xf : !llvm.ptr, %ef : f32, %df : f
// CHECK-LABEL: @omp_atomic_compare_capture_int_eq
// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
- // Integer equality prefix compare+capture → cmpxchg + select(success, E, old) + store
+ // Integer equality prefix compare+capture → cmpxchg + store old value
// CHECK: %[[RES:.*]] = cmpxchg ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
- // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
- // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
- // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+ // CHECK: store i32 %[[OLD]], ptr %[[V]]
omp.atomic.capture {
omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
omp.atomic.compare %x : !llvm.ptr {
@@ -2821,12 +2819,10 @@ llvm.func @omp_atomic_compare_capture_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e
// CHECK-LABEL: @omp_atomic_compare_capture_weak_int_eq
// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]], i32 %[[D:.*]])
llvm.func @omp_atomic_compare_capture_weak_int_eq(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32, %d : i32) {
- // Integer equality weak prefix compare+capture → cmpxchg + select(success, E, old) + store
+ // Integer equality weak prefix compare+capture → cmpxchg + store old value
// CHECK: %[[RES:.*]] = cmpxchg weak ptr %[[X]], i32 %[[E]], i32 %[[D]] monotonic monotonic
// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
- // CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RES]], 1
- // CHECK: %[[CAPTURED:.*]] = select i1 %[[SUCCESS]], i32 %[[E]], i32 %[[OLD]]
- // CHECK: store i32 %[[CAPTURED]], ptr %[[V]]
+ // CHECK: store i32 %[[OLD]], ptr %[[V]]
omp.atomic.capture {
omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
omp.atomic.compare %x : !llvm.ptr {
>From 101b9fa02882686a21d346ef12e541c717ca605b Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 30 Jun 2026 14:00:46 -0500
Subject: [PATCH 07/11] Handling real and logical types.
---
flang/lib/Lower/OpenMP/Atomic.cpp | 117 ++++++--
.../Integration/OpenMP/atomic-compare.f90 | 58 ++++
flang/test/Lower/OpenMP/atomic-compare.f90 | 83 +++++-
flang/test/Parser/OpenMP/atomic-unparse.f90 | 12 +
.../Interfaces/AtomicInterfaces.td | 13 +
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 252 +++++++++++-------
mlir/test/Dialect/OpenMP/ops.mlir | 24 ++
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 48 ++++
8 files changed, 470 insertions(+), 137 deletions(-)
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 209871abd52b6..3811ea3d40bbd 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -375,6 +375,8 @@ genAtomicRead(lower::AbstractConverter &converter,
fir::getBase(converter.genExprValue(assign.rhs, stmtCtx, &loc));
converter.resetExprOverrides();
+ if (value.getType() != storeType)
+ value = builder.createConvert(loc, storeType, value);
fir::StoreOp::create(builder, loc, value, storeAddr);
}
return op;
@@ -634,6 +636,45 @@ void Fortran::lower::omp::lowerAtomic(
},
rel->u);
}
+
+ // Fortran uses .eqv./.neqv. for logical equality/inequality, which are
+ // LogicalOperation expressions rather than Relational expressions.
+ if (!expectedExprStorage) {
+ if (const auto *someLogical =
+ evaluate::UnwrapExpr<evaluate::Expr<evaluate::SomeLogical>>(
+ *cond)) {
+ common::visit(
+ [&](const auto &kindLogical) {
+ using LogicalExpr = std::decay_t<decltype(kindLogical)>;
+ constexpr int K = LogicalExpr::Result::kind;
+ if (const auto *logOp =
+ std::get_if<evaluate::LogicalOperation<K>>(
+ &kindLogical.u)) {
+ if (logOp->logicalOperator == common::LogicalOperator::Eqv ||
+ logOp->logicalOperator == common::LogicalOperator::Neqv) {
+ relOpr =
+ (logOp->logicalOperator == common::LogicalOperator::Eqv)
+ ? common::RelationalOperator::EQ
+ : common::RelationalOperator::NE;
+ using Operand =
+ typename evaluate::LogicalOperation<K>::Operand;
+ auto leftExpr = evaluate::AsGenericExpr(
+ evaluate::Expr<Operand>{logOp->left()});
+ auto rightExpr = evaluate::AsGenericExpr(
+ evaluate::Expr<Operand>{logOp->right()});
+ if (evaluate::IsSameOrConvertOf(rightExpr, atom)) {
+ expectedExprStorage = std::move(leftExpr);
+ relOpr = reverseRelOp(relOpr);
+ } else {
+ expectedExprStorage = std::move(rightExpr);
+ }
+ }
+ }
+ },
+ someLogical->u);
+ }
+ }
+
if (!expectedExprStorage) {
mlir::emitError(loc, "internal error: atomic compare condition is not a "
"recognized relational expression");
@@ -647,6 +688,20 @@ void Fortran::lower::omp::lowerAtomic(
expectedVal = builder.createConvert(loc, elemTypeOfX, expectedVal);
}
+ // For logical types, convert address and expected value to integer
+ // type here (above the atomic compare region) so that the region
+ // only contains arith.cmpi eq on integers. If done inside the region,
+ // fir.convert logical<4> -> i32 would lower to `icmp ne %val, 0`
+ // which violates the atomic compare verifier's expectations.
+ if (mlir::isa<fir::LogicalType>(elemTypeOfX)) {
+ unsigned kind = mlir::cast<fir::LogicalType>(elemTypeOfX).getFKind();
+ mlir::Type intTy = builder.getIntegerType(kind * 8);
+ mlir::Type intRefTy = builder.getRefType(intTy);
+ atomAddr = builder.createConvert(loc, intRefTy, atomAddr);
+ expectedVal = builder.createConvert(loc, intTy, expectedVal);
+ elemTypeOfX = intTy;
+ }
+
// If this is a compare+capture, determine the ordering of ops.
// Pattern 1 (prefix): v = x; if (x == e) x = d → read first
// Pattern 2 (postfix): if (x == e) x = d; v = x → compare first
@@ -694,6 +749,39 @@ void Fortran::lower::omp::lowerAtomic(
})) {
weakAttr = builder.getUnitAttr();
}
+
+ // Extract write assignment (x = d) and generate desired value (d)
+ // before creating the compare region, so that d is defined outside
+ // the region and any intermediate conversions (e.g., logical-to-integer
+ // truthiness normalization) don't appear inside the atomic compare block.
+ [[maybe_unused]] int writeActionCond = 0;
+ const evaluate::Assignment *writeAssign = nullptr;
+ if (analysis.op0.what & analysis.Write) {
+ writeAssign = get(analysis.op0.assign);
+ writeActionCond = analysis.op0.what;
+ }
+ if (!writeAssign && (analysis.op1.what & analysis.Write)) {
+ writeAssign = get(analysis.op1.assign);
+ writeActionCond = analysis.op1.what;
+ }
+ if (!writeAssign) {
+ mlir::emitError(loc,
+ "internal error: atomic compare has no write assignment");
+ return;
+ }
+ assert((writeActionCond & analysis.IfTrue) &&
+ "atomic compare write should be conditioned on IfTrue");
+
+ // Generate desiredVal before the capture/compare region so any
+ // intermediate ops (loads, conversions) don't pollute the atomic blocks.
+ fir::FirOpBuilder::InsertPoint savedIP = builder.saveInsertionPoint();
+ builder.restoreInsertionPoint(preAt);
+ mlir::Value desiredVal =
+ fir::getBase(converter.genExprValue(writeAssign->rhs, stmtCtx, &loc));
+ if (desiredVal.getType() != elemTypeOfX)
+ desiredVal = builder.createConvert(loc, elemTypeOfX, desiredVal);
+ builder.restoreInsertionPoint(savedIP);
+
mlir::Operation *atomicOp = mlir::omp::AtomicCompareOp::create(
builder, loc, atomAddr, weakAttr, hint,
makeMemOrderAttr(converter, memOrder));
@@ -721,35 +809,6 @@ void Fortran::lower::omp::lowerAtomic(
return;
}
- // Check for presence of Assignment (x = d) and wether it is being invoked
- // only for IfTrue condition.
-
- // writeActionCond is a bitmask combining the following flags:
- // 1) the action type (Read/Write/Update)
- // 2) condition (IfTrue/IfFalse)
- [[maybe_unused]] int writeActionCond = 0;
- const evaluate::Assignment *writeAssign = nullptr;
- if (analysis.op0.what & analysis.Write) {
- writeAssign = get(analysis.op0.assign);
- writeActionCond = analysis.op0.what;
- }
- if (!writeAssign && (analysis.op1.what & analysis.Write)) {
- writeAssign = get(analysis.op1.assign);
- writeActionCond = analysis.op1.what;
- }
- if (!writeAssign) {
- mlir::emitError(loc,
- "internal error: atomic compare has no write assignment");
- return;
- }
- assert((writeActionCond & analysis.IfTrue) &&
- "atomic compare write should be conditioned on IfTrue");
-
- // Generate new/desired value of x e.g. x = d
- mlir::Value desiredVal =
- fir::getBase(converter.genExprValue(writeAssign->rhs, stmtCtx, &loc));
- if (desiredVal.getType() != elemTypeOfX)
- desiredVal = builder.createConvert(loc, elemTypeOfX, desiredVal);
mlir::Value newVal = mlir::arith::SelectOp::create(builder, loc, cmpResult,
desiredVal, blockArg);
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index 4ae1b9a1f1e52..f30349c42e33e 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -336,3 +336,61 @@ subroutine atomic_compare_capture_fail_only(x, e, d, v)
!$omp end atomic
end
+! Real equality compare+capture (postfix): if(x==e) x=d; v=x
+! v captures new value of x (d if success, old if fail)
+!CHECK-LABEL: define void @atomic_compare_capture_real_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: %[[EVAL:.*]] = load float, ptr %[[E]]{{.*}}
+!CHECK: %[[DVAL:.*]] = load float, ptr %[[D]]{{.*}}
+!CHECK: %[[EBITS:.*]] = bitcast float %[[EVAL]] to i32
+!CHECK: %[[DBITS:.*]] = bitcast float %[[DVAL]] to i32
+!CHECK: %[[XLOAD:.*]] = load atomic i32, ptr %[[X]] monotonic{{.*}}
+!CHECK: %[[XFP:.*]] = bitcast i32 %[[XLOAD]] to float
+! Part 1: NaN check - if either x or e is NaN, comparison fails
+!CHECK: %[[E_ISNAN:.*]] = fcmp uno float %[[EVAL]], %[[EVAL]]
+!CHECK: %[[X_ISNAN:.*]] = fcmp uno float %[[XFP]], %[[XFP]]
+!CHECK: %[[EITHER_NAN:.*]] = or i1 %[[E_ISNAN]], %[[X_ISNAN]]
+!CHECK: br i1 %[[EITHER_NAN]], label %[[NAN_BB:.*]], label %[[NOTNAN_BB:.*]]
+!CHECK: [[NAN_BB]]:
+!CHECK: br label %[[EXIT_BB:.*]]
+! Part 2: Both-zero check - handles +0.0 vs -0.0 (same value, different bits)
+!CHECK: [[NOTNAN_BB]]:
+!CHECK: %[[XISZERO:.*]] = fcmp oeq float %[[XFP]], 0.000000e+00
+!CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[EVAL]], 0.000000e+00
+!CHECK: %[[BOTHZERO:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+!CHECK: br i1 %[[BOTHZERO]], label %[[ZERO_BB:.*]], label %[[NORMAL_BB:.*]]
+!CHECK: [[ZERO_BB]]:
+!CHECK: %[[ZERORES:.*]] = cmpxchg ptr %[[X]], i32 %[[XLOAD]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+!CHECK: br label %[[EXIT_BB]]
+! Part 3: Normal compare - standard cmpxchg with bitcasted expected value
+!CHECK: [[NORMAL_BB]]:
+!CHECK: %[[NORMRES:.*]] = cmpxchg ptr %[[X]], i32 %[[EBITS]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+!CHECK: br label %[[EXIT_BB]]
+! Exit: select v = (success ? d : old_x)
+!CHECK: [[EXIT_BB]]:
+!CHECK: %[[OLD:.*]] = phi i32 {{.*}}
+!CHECK: %[[OK:.*]] = phi i1 {{.*}}
+!CHECK: %[[OLDFP:.*]] = bitcast i32 %[[OLD]] to float
+!CHECK: %[[VVAL:.*]] = select i1 %[[OK]], float %[[DVAL]], float %[[OLDFP]]
+!CHECK: store float %[[VVAL]], ptr %[[V]]{{.*}}
+subroutine atomic_compare_capture_real(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! Logical .eqv. compare+capture (postfix): if(x.eqv.e) x=d; v=x
+! Logicals are compared as integers after truthiness normalization
+!CHECK-LABEL: define void @atomic_compare_capture_logical_(
+!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]], ptr noalias %[[V:.*]])
+!CHECK: cmpxchg ptr %[[X]], i32 %{{.*}}, i32 %{{.*}} monotonic monotonic{{.*}}
+subroutine atomic_compare_capture_logical(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) x = d
+ v = x
+ !$omp end atomic
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index c770605954539..1219d05296e4d 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -10,10 +10,10 @@
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -31,10 +31,10 @@ subroutine atomic_compare_int_eq(x, e, d)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<f32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
! CHECK: ^bb0(%[[XVAL:.*]]: f32):
! CHECK: %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<f32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
! CHECK: omp.yield(%[[SEL]] : f32)
! CHECK: }
@@ -52,10 +52,10 @@ subroutine atomic_compare_float_eq(x, e, d)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<complex<f32>>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<complex<f32>>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<complex<f32>> {
! CHECK: ^bb0(%[[XVAL:.*]]: complex<f32>):
! CHECK: %[[CMP:.*]] = fir.cmpc "oeq", %[[XVAL]], %[[EVAL]] {fastmath = #arith.fastmath<contract>} : complex<f32>
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<complex<f32>>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : complex<f32>
! CHECK: omp.yield(%[[SEL]] : complex<f32>)
! CHECK: }
@@ -71,10 +71,10 @@ subroutine atomic_compare_complex_eq(x, e, d)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi slt, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -90,10 +90,10 @@ subroutine atomic_compare_int_lt(x, e)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -109,10 +109,10 @@ subroutine atomic_compare_int_gt(x, e)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
! CHECK: ^bb0(%[[XVAL:.*]]: f32):
! CHECK: %[[CMP:.*]] = arith.cmpf olt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
-! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : f32
! CHECK: omp.yield(%[[SEL]] : f32)
! CHECK: }
@@ -128,10 +128,10 @@ subroutine atomic_compare_float_lt(x, e)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<f32> {
! CHECK: ^bb0(%[[XVAL:.*]]: f32):
! CHECK: %[[CMP:.*]] = arith.cmpf ogt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
-! CHECK: %[[EVAL2:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[EVAL2]], %[[XVAL]] : f32
! CHECK: omp.yield(%[[SEL]] : f32)
! CHECK: }
@@ -149,10 +149,10 @@ subroutine atomic_compare_float_gt(x, e)
! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.compare memory_order(relaxed) %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: } {{.*}}weak{{.*}}
@@ -172,12 +172,12 @@ subroutine atomic_compare_int_eq_weak(x, e, d)
! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.capture memory_order(relaxed) {
! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -200,12 +200,12 @@ subroutine atomic_compare_capture_int_eq(x, e, d, v)
! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.capture memory_order(relaxed) {
! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: } {{.*}}weak{{.*}}
@@ -228,11 +228,11 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.capture memory_order(relaxed) {
! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -256,11 +256,11 @@ subroutine atomic_compare_capture_postfix(x, e, d, v)
! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: omp.atomic.capture memory_order(relaxed) {
! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
! CHECK: ^bb0(%[[XVAL:.*]]: i32):
! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
-! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
! CHECK: omp.yield(%[[SEL]] : i32)
! CHECK: }
@@ -276,3 +276,62 @@ subroutine atomic_compare_capture_fail_only(x, e, d, v)
end if
!$omp end atomic
end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<f32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<f32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<f32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<f32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<f32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: f32):
+! CHECK: %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
+! CHECK: omp.yield(%[[SEL]] : f32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine atomic_compare_capture_real(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[ELOAD:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK: %[[XCONV:.*]] = fir.convert %[[X_DECL]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! CHECK: %[[ECONV:.*]] = fir.convert %[[ELOAD]] : (!fir.logical<4>) -> i32
+! CHECK: %[[DLOAD:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK: %[[DCONV:.*]] = fir.convert %[[DLOAD]] : (!fir.logical<4>) -> i32
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[XCONV]] : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[ECONV]] : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DCONV]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} = %[[XCONV]] : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine atomic_compare_capture_logical(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) x = d
+ v = x
+ !$omp end atomic
+end
diff --git a/flang/test/Parser/OpenMP/atomic-unparse.f90 b/flang/test/Parser/OpenMP/atomic-unparse.f90
index 521c1bb2811e2..0d30cd48035ea 100644
--- a/flang/test/Parser/OpenMP/atomic-unparse.f90
+++ b/flang/test/Parser/OpenMP/atomic-unparse.f90
@@ -4,6 +4,7 @@ program main
implicit none
integer :: i, j = 10
integer :: k
+ logical :: l1, l2, l3, l4
!READ
!$omp atomic read
i = j
@@ -225,6 +226,12 @@ program main
end if
!$omp end atomic
+!COMPARE CAPTURE LOGICAL
+!$omp atomic compare capture
+ if (l1 .eqv. l2) l1 = l3
+ l4 = l1
+!$omp end atomic
+
!ATOMIC
!$omp atomic
i = j
@@ -342,6 +349,11 @@ end program main
!CHECK: !$OMP ATOMIC COMPARE CAPTURE
!CHECK: !$OMP END ATOMIC
+!COMPARE CAPTURE LOGICAL
+
+!CHECK: !$OMP ATOMIC COMPARE CAPTURE
+!CHECK: !$OMP END ATOMIC
+
!ATOMIC
!CHECK: !$OMP ATOMIC
!CHECK: !$OMP ATOMIC SEQ_CST
diff --git a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
index 5b1527bcb84ec..a61c43e2fd9cd 100644
--- a/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
+++ b/mlir/include/mlir/Dialect/OpenACCMPCommon/Interfaces/AtomicInterfaces.td
@@ -488,6 +488,19 @@ def AtomicCompareOpInterface : OpInterface<"AtomicCompareOpInterface"> {
}
}
break;
+ } else if (llvm::is_contained(
+ {"arith.maxsi", "arith.minsi", "arith.maxui",
+ "arith.minui", "arith.maximumf", "arith.minimumf",
+ "llvm.intr.smax", "llvm.intr.smin",
+ "llvm.intr.umax", "llvm.intr.umin",
+ "llvm.intr.maxnum", "llvm.intr.minnum"},
+ opName)) {
+ // The canonicalizer may fold select(cmpi slt, e, x) into
+ // maxsi/minsi (or the float equivalents). After fir-to-llvm-ir
+ // these become llvm.intr.smax/smin etc. All are valid
+ // representations of atomic compare min/max operations.
+ foundComparison = true;
+ break;
}
}
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index e1fb08d780753..8e49571cd2461 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -12,6 +12,7 @@
//===----------------------------------------------------------------------===//
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
#include "mlir/Analysis/TopologicalSortUtils.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
@@ -5369,6 +5370,102 @@ convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
}
}
+/// Holds the extracted comparison pattern information from an atomic compare
+/// region.
+struct AtomicComparePatternInfo {
+ llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+ llvm::Value *eVal = nullptr;
+ llvm::Value *dVal = nullptr;
+ bool isXBinopExpr = false;
+ bool isSigned = false;
+};
+
+/// Extract comparison predicate, expected value (e), desired value (d), and
+/// related flags from an atomic compare region block by scanning for
+/// icmp/fcmp/select/min/max operations.
+static LogicalResult extractAtomicComparePattern(
+ Block &block,
+ llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
+ omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
+ for (Operation &op : block.getOperations()) {
+ // Pre-filter: skip icmps that don't involve the block argument
+ // (e.g., truthiness extractions from logical-to-integer conversion).
+ if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op);
+ icmpOp && icmpOp.getOperand(0) != block.getArgument(0) &&
+ icmpOp.getOperand(1) != block.getArgument(0))
+ continue;
+
+ LogicalResult result =
+ llvm::TypeSwitch<Operation *, LogicalResult>(&op)
+ .Case<LLVM::ICmpOp>([&](LLVM::ICmpOp icmpOp) -> LogicalResult {
+ auto maybeOp =
+ convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
+ if (!maybeOp)
+ return atomicCompareOp.emitError(
+ "unsupported comparison predicate in atomic compare");
+ info.compareOp = *maybeOp;
+ LLVM::ICmpPredicate pred = icmpOp.getPredicate();
+ info.isSigned = (pred == LLVM::ICmpPredicate::slt ||
+ pred == LLVM::ICmpPredicate::sgt ||
+ pred == LLVM::ICmpPredicate::sle ||
+ pred == LLVM::ICmpPredicate::sge);
+ info.isXBinopExpr =
+ (icmpOp.getOperand(0) == block.getArgument(0));
+ mlir::Value eOperand = info.isXBinopExpr ? icmpOp.getOperand(1)
+ : icmpOp.getOperand(0);
+ info.eVal = materializeValue(eOperand);
+ return success();
+ })
+ .Case<LLVM::FCmpOp>([&](LLVM::FCmpOp fcmpOp) -> LogicalResult {
+ auto maybeOp =
+ convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
+ if (!maybeOp)
+ return atomicCompareOp.emitError(
+ "unsupported comparison predicate in atomic compare");
+ info.compareOp = *maybeOp;
+ info.isXBinopExpr =
+ (fcmpOp.getOperand(0) == block.getArgument(0));
+ mlir::Value eOperand = info.isXBinopExpr ? fcmpOp.getOperand(1)
+ : fcmpOp.getOperand(0);
+ info.eVal = materializeValue(eOperand);
+ return success();
+ })
+ .Case<LLVM::SelectOp>([&](LLVM::SelectOp selectOp) {
+ if (!info.dVal)
+ info.dVal = materializeValue(selectOp.getTrueValue());
+ return success();
+ })
+ .Case<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+ mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+ mlir::arith::MaximumFOp, mlir::arith::MinimumFOp,
+ LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp, LLVM::UMinOp,
+ LLVM::MaxNumOp, LLVM::MinNumOp>([&](Operation *) {
+ // Canonicalized min/max ops (arith or LLVM intrinsic form).
+ // max(x,e) came from slt/ult/olt -> OMPAtomicCompareOp::MIN
+ // min(x,e) came from sgt/ugt/ogt -> OMPAtomicCompareOp::MAX
+ // (OMPIRBuilder inverts: MIN->atomicrmw max, MAX->atomicrmw min)
+ bool isMax = isa<mlir::arith::MaxSIOp, mlir::arith::MaxUIOp,
+ mlir::arith::MaximumFOp, LLVM::SMaxOp,
+ LLVM::UMaxOp, LLVM::MaxNumOp>(op);
+ info.compareOp = isMax ? llvm::omp::OMPAtomicCompareOp::MIN
+ : llvm::omp::OMPAtomicCompareOp::MAX;
+ info.isSigned = isa<mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+ LLVM::SMaxOp, LLVM::SMinOp>(op);
+ info.isXBinopExpr = (op.getOperand(0) == block.getArgument(0));
+ mlir::Value eOperand =
+ info.isXBinopExpr ? op.getOperand(1) : op.getOperand(0);
+ info.eVal = materializeValue(eOperand);
+ info.dVal = info.eVal;
+ return success();
+ })
+ .Default([](Operation *) { return success(); });
+
+ if (failed(result))
+ return result;
+ }
+ return success();
+}
+
static LogicalResult
convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
llvm::IRBuilderBase &builder,
@@ -5409,7 +5506,11 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
// Pre-translate non-pattern operations inside the compare region.
auto isAtomicComparePatternOp = [](Operation &op) {
return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
- LLVM::OrOp>(op);
+ LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
+ LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
+ mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+ mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+ mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
};
for (Operation &op : block.without_terminator()) {
if (isAtomicComparePatternOp(op))
@@ -5443,47 +5544,16 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
};
// Extract comparison predicate, eVal, and dVal from the region.
- llvm::omp::OMPAtomicCompareOp compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
- llvm::Value *eVal = nullptr;
- llvm::Value *dVal = nullptr;
- bool isXBinopExpr = false;
+ AtomicComparePatternInfo patternInfo;
+ if (failed(extractAtomicComparePattern(block, materializeValue,
+ atomicCompareOp, patternInfo)))
+ return failure();
- for (Operation &op : block.getOperations()) {
- if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
- auto maybeOp =
- convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
- if (!maybeOp)
- return atomicCompareOp.emitError(
- "unsupported comparison predicate in atomic compare");
- compareOp = *maybeOp;
-
- LLVM::ICmpPredicate pred = icmpOp.getPredicate();
- isSigned = (pred == LLVM::ICmpPredicate::slt ||
- pred == LLVM::ICmpPredicate::sgt ||
- pred == LLVM::ICmpPredicate::sle ||
- pred == LLVM::ICmpPredicate::sge);
-
- isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
- mlir::Value eOperand =
- isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
- eVal = materializeValue(eOperand);
- } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
- auto maybeOp =
- convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
- if (!maybeOp)
- return atomicCompareOp.emitError(
- "unsupported comparison predicate in atomic compare");
- compareOp = *maybeOp;
-
- isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
- mlir::Value eOperand =
- isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
- eVal = materializeValue(eOperand);
- } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
- if (!dVal)
- dVal = materializeValue(selectOp.getTrueValue());
- }
- }
+ llvm::omp::OMPAtomicCompareOp compareOp = patternInfo.compareOp;
+ llvm::Value *eVal = patternInfo.eVal;
+ llvm::Value *dVal = patternInfo.dVal;
+ bool isXBinopExpr = patternInfo.isXBinopExpr;
+ isSigned = patternInfo.isSigned;
if (!eVal)
return atomicCompareOp.emitError(
@@ -5529,17 +5599,42 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
// Postfix: v = select(success, D, old) — captures new value of x.
if (isPostfixCapture && !isFailOnly) {
llvm::BasicBlock *curBB = builder.GetInsertBlock();
- llvm::Instruction *cmpxchgInst = nullptr;
+ llvm::Value *oldVal = nullptr;
+ llvm::Value *successVal = nullptr;
+
+ // Integer path (and non-HandleFPNegZero FP path): a single cmpxchg
+ // lives in the current block.
for (auto &inst : llvm::reverse(*curBB)) {
if (isa<llvm::AtomicCmpXchgInst>(&inst)) {
- cmpxchgInst = &inst;
+ oldVal = builder.CreateExtractValue(&inst, /*Idxs=*/0);
+ successVal = builder.CreateExtractValue(&inst, /*Idxs=*/1);
break;
}
}
- assert(cmpxchgInst && "expected cmpxchg instruction");
- llvm::Value *oldVal = builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/0);
- llvm::Value *successVal =
- builder.CreateExtractValue(cmpxchgInst, /*Idxs=*/1);
+
+ // FP HandleFPNegZero path: the OMPIRBuilder emits a multi-block
+ // structure (NaN / ±0.0 handling) with cmpxchg in predecessor
+ // blocks. Results are merged via PHI nodes in the current (exit)
+ // block: an i1 PHI for success and a bitcast of an integer PHI
+ // for the old FP value.
+ if (!oldVal) {
+ for (auto &inst : *curBB) {
+ auto *phi = dyn_cast<llvm::PHINode>(&inst);
+ if (!phi)
+ break;
+ if (phi->getType()->isIntegerTy(1))
+ successVal = phi;
+ }
+ for (auto &inst : *curBB) {
+ if (auto *bc = dyn_cast<llvm::BitCastInst>(&inst)) {
+ oldVal = bc;
+ break;
+ }
+ }
+ }
+
+ assert(oldVal && "expected cmpxchg or PHI+bitcast for compare capture");
+ assert(successVal && "expected success flag for compare capture");
llvm::Value *newVal = builder.CreateSelect(successVal, dVal, oldVal);
builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
}
@@ -5686,7 +5781,11 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
auto isAtomicComparePatternOp = [](Operation &op) {
return llvm::isa<LLVM::ICmpOp, LLVM::FCmpOp, LLVM::SelectOp, LLVM::AndOp,
- LLVM::OrOp>(op);
+ LLVM::OrOp, LLVM::SMaxOp, LLVM::SMinOp, LLVM::UMaxOp,
+ LLVM::UMinOp, LLVM::MaxNumOp, LLVM::MinNumOp,
+ mlir::arith::MaxSIOp, mlir::arith::MinSIOp,
+ mlir::arith::MaxUIOp, mlir::arith::MinUIOp,
+ mlir::arith::MaximumFOp, mlir::arith::MinimumFOp>(op);
};
// Pre-translate operations inside the region that compute e and d (e.g.,
@@ -5859,54 +5958,15 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
}
return success();
} else {
-
- for (Operation &op : block.getOperations()) {
- if (auto icmpOp = dyn_cast<LLVM::ICmpOp>(op)) {
- auto maybeOp =
- convertICmpPredicateToAtomicCompareOp(icmpOp.getPredicate());
- if (!maybeOp)
- return atomicCompareOp.emitError(
- "unsupported comparison predicate in atomic compare");
- compareOp = *maybeOp;
-
- LLVM::ICmpPredicate pred = icmpOp.getPredicate();
- isSigned = (pred == LLVM::ICmpPredicate::slt ||
- pred == LLVM::ICmpPredicate::sgt ||
- pred == LLVM::ICmpPredicate::sle ||
- pred == LLVM::ICmpPredicate::sge);
-
- // Identify which operand is the block argument (x) and which is e.
- isXBinopExpr = (icmpOp.getOperand(0) == block.getArgument(0));
- mlir::Value eOperand =
- isXBinopExpr ? icmpOp.getOperand(1) : icmpOp.getOperand(0);
- eVal = materializeValue(eOperand);
- } else if (auto fcmpOp = dyn_cast<LLVM::FCmpOp>(op)) {
- auto maybeOp =
- convertFCmpPredicateToAtomicCompareOp(fcmpOp.getPredicate());
- if (!maybeOp)
- return atomicCompareOp.emitError(
- "unsupported comparison predicate in atomic compare");
- compareOp = *maybeOp;
-
- isXBinopExpr = (fcmpOp.getOperand(0) == block.getArgument(0));
- mlir::Value eOperand =
- isXBinopExpr ? fcmpOp.getOperand(1) : fcmpOp.getOperand(0);
- eVal = materializeValue(eOperand);
- } else if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
- if (!dVal)
- dVal = materializeValue(selectOp.getTrueValue());
- }
- }
- }
-
- // For non-complex patterns, also extract dVal from SelectOp.
- if (!dVal) {
- for (Operation &op : block.getOperations()) {
- if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
- dVal = materializeValue(selectOp.getTrueValue());
- break;
- }
- }
+ AtomicComparePatternInfo patternInfo;
+ if (failed(extractAtomicComparePattern(block, materializeValue,
+ atomicCompareOp, patternInfo)))
+ return failure();
+ compareOp = patternInfo.compareOp;
+ eVal = patternInfo.eVal;
+ dVal = patternInfo.dVal;
+ isXBinopExpr = patternInfo.isXBinopExpr;
+ isSigned = patternInfo.isSigned;
}
if (!eVal)
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index 6b13be481e0e0..cc40506b909b1 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -2161,6 +2161,30 @@ func.func @omp_atomic_compare_capture_fail_only(%v: memref<i32>, %x: memref<i32>
return
}
+// CHECK-LABEL: omp_atomic_compare_capture_real
+// CHECK-SAME: (%[[V:.*]]: memref<f32>, %[[X:.*]]: memref<f32>, %[[E:.*]]: f32, %[[D:.*]]: f32)
+func.func @omp_atomic_compare_capture_real(%v: memref<f32>, %x: memref<f32>, %e: f32, %d: f32) {
+ // CHECK: omp.atomic.capture {
+ // CHECK-NEXT: omp.atomic.compare %[[X]] : memref<f32> {
+ // CHECK-NEXT: ^bb0(%[[XVAL:.*]]: f32):
+ // CHECK-NEXT: %[[CMP:.*]] = arith.cmpf oeq, %[[XVAL]], %[[E]] : f32
+ // CHECK-NEXT: %[[SEL:.*]] = arith.select %[[CMP]], %[[D]], %[[XVAL]] : f32
+ // CHECK-NEXT: omp.yield(%[[SEL]] : f32)
+ // CHECK-NEXT: }
+ // CHECK-NEXT: omp.atomic.read %[[V]] = %[[X]] : memref<f32>, memref<f32>, f32
+ // CHECK-NEXT: }
+ omp.atomic.capture {
+ omp.atomic.compare %x : memref<f32> {
+ ^bb0(%xval: f32):
+ %cmp = arith.cmpf oeq, %xval, %e : f32
+ %sel = arith.select %cmp, %d, %xval : f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : memref<f32>, memref<f32>, f32
+ }
+ return
+}
+
// CHECK-LABEL: omp_sectionsop
func.func @omp_sectionsop(%data_var1 : memref<i32>, %data_var2 : memref<i32>,
%data_var3 : memref<i32>, %redn_var : !llvm.ptr) {
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 377bd88894854..b779d20f42d0c 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2882,6 +2882,54 @@ llvm.func @omp_atomic_compare_capture_fail_only(%x : !llvm.ptr, %v : !llvm.ptr,
}
// -----
+// CHECK-LABEL: @omp_atomic_compare_capture_real
+// CHECK-SAME: (ptr %[[X:.*]], ptr %[[V:.*]], float %[[E:.*]], float %[[D:.*]])
+llvm.func @omp_atomic_compare_capture_real(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32, %d : f32) {
+ // Real compare+capture: uses HandleFPNegZero multi-block path
+ // CHECK: %[[EBITS:.*]] = bitcast float %[[E]] to i32
+ // CHECK: %[[DBITS:.*]] = bitcast float %[[D]] to i32
+ // CHECK: %[[XI:.*]] = load atomic i32, ptr %[[X]] monotonic{{.*}}
+ // CHECK: %[[XFP:.*]] = bitcast i32 %[[XI]] to float
+ // Part 1: NaN check
+ // CHECK: %[[E_NAN:.*]] = fcmp uno float %[[E]], %[[E]]
+ // CHECK: %[[X_NAN:.*]] = fcmp uno float %[[XFP]], %[[XFP]]
+ // CHECK: %[[EITHER_NAN:.*]] = or i1 %[[E_NAN]], %[[X_NAN]]
+ // CHECK: br i1 %[[EITHER_NAN]], label %[[NAN:.*]], label %[[NOTNAN:.*]]
+ // CHECK: [[NAN]]:
+ // CHECK: br label %[[EXIT:.*]]
+ // Part 2: Both-zero check
+ // CHECK: [[NOTNAN]]:
+ // CHECK: %[[XISZERO:.*]] = fcmp oeq float %[[XFP]], 0.000000e+00
+ // CHECK: %[[EISZERO:.*]] = fcmp oeq float %[[E]], 0.000000e+00
+ // CHECK: %[[BOTHZERO:.*]] = and i1 %[[XISZERO]], %[[EISZERO]]
+ // CHECK: br i1 %[[BOTHZERO]], label %[[ZERO:.*]], label %[[NORMAL:.*]]
+ // CHECK: [[ZERO]]:
+ // CHECK: cmpxchg ptr %[[X]], i32 %[[XI]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+ // CHECK: br label %[[EXIT]]
+ // Part 3: Normal cmpxchg
+ // CHECK: [[NORMAL]]:
+ // CHECK: cmpxchg ptr %[[X]], i32 %[[EBITS]], i32 %[[DBITS]] monotonic monotonic{{.*}}
+ // CHECK: br label %[[EXIT]]
+ // Exit: select v = (success ? d : old_x)
+ // CHECK: [[EXIT]]:
+ // CHECK: %[[OLD:.*]] = phi i32 {{.*}}
+ // CHECK: %[[OK:.*]] = phi i1 {{.*}}
+ // CHECK: %[[OLDFP:.*]] = bitcast i32 %[[OLD]] to float
+ // CHECK: %[[VVAL:.*]] = select i1 %[[OK]], float %[[D]], float %[[OLDFP]]
+ // CHECK: store float %[[VVAL]], ptr %[[V]]{{.*}}
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : f32):
+ %cmp = llvm.fcmp "oeq" %xval, %e : f32
+ %sel = llvm.select %cmp, %d, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ }
+ llvm.return
+}
+// -----
+
// CHECK-LABEL: @omp_sections_empty
llvm.func @omp_sections_empty() -> () {
omp.sections {
>From d42a6412d158c581c76cadf7d4626b4235e60725 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Wed, 1 Jul 2026 07:25:53 -0500
Subject: [PATCH 08/11] Handling a crash in logical "omp atomic
compare-capture" inside "omp parallel". Adding some extra tests.
---
flang/lib/Semantics/check-omp-atomic.cpp | 19 ++-
.../Integration/OpenMP/atomic-compare.f90 | 27 ++++
flang/test/Lower/OpenMP/atomic-compare.f90 | 117 ++++++++++++++++++
.../test/Semantics/OpenMP/atomic-compare.f90 | 12 ++
4 files changed, 174 insertions(+), 1 deletion(-)
diff --git a/flang/lib/Semantics/check-omp-atomic.cpp b/flang/lib/Semantics/check-omp-atomic.cpp
index ec307be469982..88997df26e4d0 100644
--- a/flang/lib/Semantics/check-omp-atomic.cpp
+++ b/flang/lib/Semantics/check-omp-atomic.cpp
@@ -419,6 +419,22 @@ static bool IsMaybeAtomicWrite(const evaluate::Assignment &assign) {
return HasStorageOverlap(assign.lhs, assign.rhs) == nullptr;
}
+// Determine whether two designators refer to the same atomic variable.
+// The comparison tolerates OpenMP host-association: a shared variable
+// referenced inside a parallel region may be designated through a
+// host-associated symbol in one statement and through its ultimate symbol
+// in another, even though both denote the same storage. Comparing the
+// (ultimate) symbol vectors together with the source form distinguishes
+// different variables and different constant subscripts (e.g. a(1) vs a(2))
+// while treating host-associated and ultimate references as equal.
+static bool IsSameAtomicVariable(const SomeExpr &x, const SomeExpr &y) {
+ if (x == y) {
+ return true;
+ }
+ return evaluate::GetSymbolVector(x) == evaluate::GetSymbolVector(y) &&
+ x.AsFortran() == y.AsFortran();
+}
+
static void SetExpr(parser::TypedExpr &expr, MaybeExpr value) {
if (value) {
expr.Reset(new evaluate::GenericExprWrapper(std::move(value)),
@@ -822,7 +838,8 @@ void OmpStructureChecker::CheckAtomicCaptureAssignment(
CheckAtomicVariable(
atom, rsrc, /*checkTypeOnPointer=*/!IsPointerAssignment(capture));
// This part should have been checked prior to calling this function.
- assert(*GetConvertInput(capture.rhs) == atom &&
+ assert(GetConvertInput(capture.rhs) &&
+ IsSameAtomicVariable(*GetConvertInput(capture.rhs), atom) &&
"This cannot be a capture assignment");
CheckStorageOverlap(atom, {cap}, source);
}
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index f30349c42e33e..e98b25e4b4539 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -394,3 +394,30 @@ subroutine atomic_compare_capture_logical(x, e, d, v)
!$omp end atomic
end
+! Logical compare+capture inside a parallel region.
+!CHECK-LABEL: define void @atomic_compare_capture_logical_parallel_(
+!CHECK-SAME: ptr noalias %[[X:.*]])
+!CHECK: %[[CGEP:.*]] = getelementptr { ptr }, ptr %structArg, i32 0, i32 0
+!CHECK: store ptr %[[X]], ptr %[[CGEP]]
+!CHECK: call void {{.*}}@__kmpc_fork_call{{.*}}@atomic_compare_capture_logical_parallel_..omp_par
+!CHECK-LABEL: define internal void @atomic_compare_capture_logical_parallel_..omp_par(
+!CHECK-SAME: ptr noalias %{{.*}}, ptr noalias %{{.*}}, ptr %[[STRUCTARG:.*]])
+!CHECK: %[[GEP:.*]] = getelementptr { ptr }, ptr %[[STRUCTARG]], i32 0, i32 0
+!CHECK: %[[SHARED:.*]] = load ptr, ptr %[[GEP]]
+!CHECK: %[[RES:.*]] = cmpxchg ptr %[[SHARED]], i32 %{{.*}}, i32 %{{.*}} monotonic monotonic{{.*}}
+!CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+!CHECK: store i32 %[[OLD]], ptr %{{.*}}
+subroutine atomic_compare_capture_logical_parallel(x)
+ logical :: x, expected, desired, old_value
+ !$omp parallel private(old_value, expected, desired)
+ expected = .true.
+ desired = .false.
+ !$omp atomic compare capture
+ old_value = x
+ if (x .eqv. expected) then
+ x = desired
+ end if
+ !$omp end atomic
+ !$omp end parallel
+end
+
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index 1219d05296e4d..ccc44a370be79 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -305,6 +305,83 @@ subroutine atomic_compare_capture_real(x, e, d, v)
!$omp end atomic
end
+! Compare-capture with an explicit seq_cst memory order (prefix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_seq_cst(
+! CHECK: omp.atomic.capture memory_order(seq_cst) {
+! CHECK: omp.atomic.read %[[V_DECL:.*]]#0 = %[[X_DECL:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: }
+subroutine atomic_compare_capture_seq_cst(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture seq_cst
+ v = x
+ if (x .eq. e) x = d
+ !$omp end atomic
+end
+
+! Compare-capture with an explicit acquire memory order (postfix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_acquire(
+! CHECK: omp.atomic.capture memory_order(acquire) {
+! CHECK: omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine atomic_compare_capture_acquire(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture acquire
+ if (x .eq. e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! Compare-capture with an explicit release memory order (prefix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_release(
+! CHECK: omp.atomic.capture memory_order(release) {
+! CHECK: omp.atomic.read %[[V_DECL:.*]]#0 = %[[X_DECL:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: }
+subroutine atomic_compare_capture_release(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture release
+ v = x
+ if (x .eq. e) x = d
+ !$omp end atomic
+end
+
+! Compare-capture with an explicit acq_rel memory order (postfix read form).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_acq_rel(
+! CHECK: omp.atomic.capture memory_order(acq_rel) {
+! CHECK: omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine atomic_compare_capture_acq_rel(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture acq_rel
+ if (x .eq. e) x = d
+ v = x
+ !$omp end atomic
+end
+
+
! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical(
! CHECK-SAME: %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"},
! CHECK-SAME: %[[E:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "e"},
@@ -335,3 +412,43 @@ subroutine atomic_compare_capture_logical(x, e, d, v)
v = x
!$omp end atomic
end
+
+! Logical compare-capture inside a parallel region. The shared atom 'x' is
+! host-associated, and the capture reads it while the conditional update writes
+! it. Verify the atom is converted to i32 once (above the atomic region) and
+! that the capture reads then compares the same converted reference.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_logical_parallel(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x"})
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}Ex"
+! CHECK: omp.parallel private(
+! CHECK: %[[OLD_DECL:.*]]:2 = hlfir.declare %{{.*}}Eold_value"
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Eexpected"
+! CHECK: %[[ELOAD:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK: %[[XCONV:.*]] = fir.convert %[[X_DECL]]#0 : (!fir.ref<!fir.logical<4>>) -> !fir.ref<i32>
+! CHECK: %[[ECONV:.*]] = fir.convert %[[ELOAD]] : (!fir.logical<4>) -> i32
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %[[ATOMVAL:.*]] = %[[XCONV]] : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[XCONV]] : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[ECONV]] : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: }
+! CHECK: %[[RES:.*]] = fir.load %[[ATOMVAL]] : !fir.ref<i32>
+! CHECK: %[[RESCONV:.*]] = fir.convert %[[RES]] : (i32) -> !fir.logical<4>
+! CHECK: fir.store %[[RESCONV]] to %[[OLD_DECL]]#0 : !fir.ref<!fir.logical<4>>
+! CHECK: omp.terminator
+subroutine atomic_compare_capture_logical_parallel(x)
+ logical :: x, expected, desired, old_value
+ !$omp parallel private(old_value, expected, desired)
+ expected = .true.
+ desired = .false.
+ !$omp atomic compare capture
+ old_value = x
+ if (x .eqv. expected) then
+ x = desired
+ end if
+ !$omp end atomic
+ !$omp end parallel
+end
diff --git a/flang/test/Semantics/OpenMP/atomic-compare.f90 b/flang/test/Semantics/OpenMP/atomic-compare.f90
index 0e53729c2f02a..5cee2359f52be 100644
--- a/flang/test/Semantics/OpenMP/atomic-compare.f90
+++ b/flang/test/Semantics/OpenMP/atomic-compare.f90
@@ -9,6 +9,7 @@
real a, b, c
logical :: r, s
+ logical :: lx, le, ld, lo
a = 1.0
b = 2.0
c = 3.0
@@ -58,6 +59,17 @@
if (r) b = c
!$omp end atomic
+ ! Compare-capture with a LOGICAL atom using .eqv. inside a parallel region.
+ ! Regression: the host-associated atom (from the update) must be recognized
+ ! as the same variable as the captured one, even though OpenMP resolves them
+ ! through different (but ultimately identical) symbols.
+ !$omp atomic compare capture
+ lo = lx
+ if (lx .eqv. le) then
+ lx = ld
+ end if
+ !$omp end atomic
+
! Check for error conditions:
!ERROR: At most one SEQ_CST clause can appear on the ATOMIC directive
!$omp atomic seq_cst seq_cst compare
>From b6f101ded4b5847a466a27c6a37185a3203b6ebc Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Fri, 10 Jul 2026 00:50:42 -0500
Subject: [PATCH 09/11] Added exhaustive tests covering all atomic compare
capture combinations Handling changes for atomic compare capture were not
covered earlier. Completed runs of llvm-test-suite and Fujitsu flang
test-suite.
---
flang/lib/Lower/OpenMP/Atomic.cpp | 14 +
flang/test/Fir/atomic-compare-capture.fir | 343 ++++++++++++++
.../OpenMP/atomic-compare-capture.f90 | 443 ++++++++++++++++++
.../Todo/atomic-compare-capture-result.f90 | 20 +
.../Lower/OpenMP/Todo/atomic-compare-fail.f90 | 8 -
.../Lower/OpenMP/atomic-compare-capture.f90 | 356 ++++++++++++++
flang/test/Lower/OpenMP/atomic-compare.f90 | 116 +++++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 398 ++++++++++++----
.../LLVMIR/openmp-atomic-compare-capture.mlir | 407 ++++++++++++++++
9 files changed, 2007 insertions(+), 98 deletions(-)
create mode 100644 flang/test/Fir/atomic-compare-capture.fir
create mode 100644 flang/test/Integration/OpenMP/atomic-compare-capture.f90
create mode 100644 flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
create mode 100644 flang/test/Lower/OpenMP/atomic-compare-capture.f90
create mode 100644 mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
diff --git a/flang/lib/Lower/OpenMP/Atomic.cpp b/flang/lib/Lower/OpenMP/Atomic.cpp
index 3811ea3d40bbd..ca37eb4a73375 100644
--- a/flang/lib/Lower/OpenMP/Atomic.cpp
+++ b/flang/lib/Lower/OpenMP/Atomic.cpp
@@ -606,6 +606,20 @@ void Fortran::lower::omp::lowerAtomic(
TODO(loc, "Compound clauses of OpenMP ATOMIC COMPARE");
}
+ // The comparison-result forms of atomic compare
+ // (e.g. `r = x == e;
+ // if (r) x = d`)
+ // are accepted by semantics, but the assignment to `r` is
+ // not captured by the atomic analysis and would be silently dropped.
+ // Detect the extra assignment here and emit an explicit TODO instead of
+ // generating incorrect code.
+ if (!construct.IsCapture()) {
+ const parser::Block &body = std::get<parser::Block>(construct.t);
+ if (body.size() > 1)
+ TODO(loc, "atomic compare capturing the comparison result "
+ "(e.g. 'r = x == e')");
+ }
+
common::RelationalOperator relOpr = common::RelationalOperator::EQ;
std::optional<semantics::SomeExpr> expectedExprStorage;
bool isUnsigned = false;
diff --git a/flang/test/Fir/atomic-compare-capture.fir b/flang/test/Fir/atomic-compare-capture.fir
new file mode 100644
index 0000000000000..65264fbc688a8
--- /dev/null
+++ b/flang/test/Fir/atomic-compare-capture.fir
@@ -0,0 +1,343 @@
+// RUN: fir-opt --split-input-file --cfg-conversion --fir-to-llvm-ir="target=aarch64-unknown-linux-gnu" %s | FileCheck %s
+
+// Test FIR-to-LLVM-dialect conversion of OpenMP atomic compare-capture: the
+// !fir.ref operands become !llvm.ptr and the compare region's arith ops become
+// their llvm-dialect equivalents, while the omp.atomic.capture / .compare /
+// .read structure is preserved for the later MLIR-to-LLVM-IR translation.
+
+//
+// Integer postfix compare-capture: {compare, read}
+func.func @cc_int_postfix(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+ %eval = fir.load %e : !fir.ref<i32>
+ %dval = fir.load %d : !fir.ref<i32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<i32> {
+ ^bb0(%xval: i32):
+ %c = arith.cmpi eq, %xval, %eval : i32
+ %s = arith.select %c, %dval, %xval : i32
+ omp.yield(%s : i32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_postfix(
+// CHECK-SAME: %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK: %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> i32
+// CHECK: %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> i32
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK: ^bb0(%[[XVAL:.*]]: i32):
+// CHECK: %[[CMP:.*]] = llvm.icmp "eq" %[[XVAL]], %[[EVAL]] : i32
+// CHECK: %[[SEL:.*]] = llvm.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i1, i32
+// CHECK: omp.yield(%[[SEL]] : i32)
+// CHECK: }
+// CHECK: omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, i32
+// CHECK: }
+
+// -----
+
+// Integer prefix compare-capture: {read, compare}
+func.func @cc_int_prefix(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+ %eval = fir.load %e : !fir.ref<i32>
+ %dval = fir.load %d : !fir.ref<i32>
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ omp.atomic.compare %x : !fir.ref<i32> {
+ ^bb0(%xval: i32):
+ %c = arith.cmpi eq, %xval, %eval : i32
+ %s = arith.select %c, %dval, %xval : i32
+ omp.yield(%s : i32)
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_prefix(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.icmp "eq"
+// CHECK: llvm.select
+// CHECK: omp.yield
+// CHECK: }
+// CHECK: }
+
+// -----
+
+// Integer fail-only compare-capture: {compare, read} with fail_only attribute.
+func.func @cc_int_failonly(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+ %eval = fir.load %e : !fir.ref<i32>
+ %dval = fir.load %d : !fir.ref<i32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<i32> {
+ ^bb0(%xval: i32):
+ %c = arith.cmpi eq, %xval, %eval : i32
+ %s = arith.select %c, %dval, %xval : i32
+ omp.yield(%s : i32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ } {fail_only}
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_failonly(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.icmp "eq"
+// CHECK: }
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK: } {fail_only}
+
+// -----
+
+// Real postfix compare-capture: !fir.ref<f32> -> !llvm.ptr, arith.cmpf -> llvm.fcmp.
+func.func @cc_real_postfix(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+ %eval = fir.load %e : !fir.ref<f32>
+ %dval = fir.load %d : !fir.ref<f32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<f32> {
+ ^bb0(%xval: f32):
+ %c = arith.cmpf oeq, %xval, %eval : f32
+ %s = arith.select %c, %dval, %xval : f32
+ omp.yield(%s : f32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_postfix(
+// CHECK-SAME: %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK: %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> f32
+// CHECK: %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> f32
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK: ^bb0(%[[XVAL:.*]]: f32):
+// CHECK: %[[CMP:.*]] = llvm.fcmp "oeq" %[[XVAL]], %[[EVAL]] : f32
+// CHECK: %[[SEL:.*]] = llvm.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i1, f32
+// CHECK: omp.yield(%[[SEL]] : f32)
+// CHECK: }
+// CHECK: omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, f32
+// CHECK: }
+
+// -----
+
+// Real prefix compare-capture.
+func.func @cc_real_prefix(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+ %eval = fir.load %e : !fir.ref<f32>
+ %dval = fir.load %d : !fir.ref<f32>
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+ omp.atomic.compare %x : !fir.ref<f32> {
+ ^bb0(%xval: f32):
+ %c = arith.cmpf oeq, %xval, %eval : f32
+ %s = arith.select %c, %dval, %xval : f32
+ omp.yield(%s : f32)
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_prefix(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.fcmp "oeq"
+// CHECK: }
+// CHECK: }
+
+// -----
+
+// Real fail-only compare-capture.
+func.func @cc_real_failonly(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %d: !fir.ref<f32>, %v: !fir.ref<f32>) {
+ %eval = fir.load %e : !fir.ref<f32>
+ %dval = fir.load %d : !fir.ref<f32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<f32> {
+ ^bb0(%xval: f32):
+ %c = arith.cmpf oeq, %xval, %eval : f32
+ %s = arith.select %c, %dval, %xval : f32
+ omp.yield(%s : f32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+ } {fail_only}
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_failonly(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.fcmp "oeq"
+// CHECK: }
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK: } {fail_only}
+
+// -----
+
+// Complex postfix compare-capture: !fir.ref<complex<f32>> -> !llvm.ptr and the
+// fir.cmpc is decomposed into per-field llvm.fcmp + llvm.and over the struct.
+func.func @cc_complex_postfix(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+ %eval = fir.load %e : !fir.ref<complex<f32>>
+ %dval = fir.load %d : !fir.ref<complex<f32>>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<complex<f32>> {
+ ^bb0(%xval: complex<f32>):
+ %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+ %s = arith.select %c, %dval, %xval : complex<f32>
+ omp.yield(%s : complex<f32>)
+ }
+ omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_postfix(
+// CHECK-SAME: %[[X:.*]]: !llvm.ptr, %[[E:.*]]: !llvm.ptr, %[[D:.*]]: !llvm.ptr, %[[V:.*]]: !llvm.ptr) {
+// CHECK: %[[EVAL:.*]] = llvm.load %[[E]] : !llvm.ptr -> !llvm.struct<(f32, f32)>
+// CHECK: %[[DVAL:.*]] = llvm.load %[[D]] : !llvm.ptr -> !llvm.struct<(f32, f32)>
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %[[X]] : !llvm.ptr {
+// CHECK: ^bb0(%[[XVAL:.*]]: !llvm.struct<(f32, f32)>):
+// CHECK: llvm.extractvalue %[[XVAL]][0]
+// CHECK: llvm.fcmp "oeq"
+// CHECK: llvm.extractvalue %[[XVAL]][1]
+// CHECK: llvm.fcmp "oeq"
+// CHECK: %[[AND:.*]] = llvm.and
+// CHECK: %[[SEL:.*]] = llvm.select %[[AND]], %[[DVAL]], %[[XVAL]] : i1, !llvm.struct<(f32, f32)>
+// CHECK: omp.yield(%[[SEL]] : !llvm.struct<(f32, f32)>)
+// CHECK: }
+// CHECK: omp.atomic.read %[[V]] = %[[X]] : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK: }
+
+// -----
+
+// Complex prefix compare-capture.
+func.func @cc_complex_prefix(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+ %eval = fir.load %e : !fir.ref<complex<f32>>
+ %dval = fir.load %d : !fir.ref<complex<f32>>
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+ omp.atomic.compare %x : !fir.ref<complex<f32>> {
+ ^bb0(%xval: complex<f32>):
+ %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+ %s = arith.select %c, %dval, %xval : complex<f32>
+ omp.yield(%s : complex<f32>)
+ }
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_prefix(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.fcmp "oeq"
+// CHECK: llvm.fcmp "oeq"
+// CHECK: llvm.and
+// CHECK: }
+// CHECK: }
+
+// -----
+
+// Complex fail-only compare-capture.
+func.func @cc_complex_failonly(%x: !fir.ref<complex<f32>>, %e: !fir.ref<complex<f32>>, %d: !fir.ref<complex<f32>>, %v: !fir.ref<complex<f32>>) {
+ %eval = fir.load %e : !fir.ref<complex<f32>>
+ %dval = fir.load %d : !fir.ref<complex<f32>>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<complex<f32>> {
+ ^bb0(%xval: complex<f32>):
+ %c = fir.cmpc "oeq", %xval, %eval : complex<f32>
+ %s = arith.select %c, %dval, %xval : complex<f32>
+ omp.yield(%s : complex<f32>)
+ }
+ omp.atomic.read %v = %x : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+ } {fail_only}
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_complex_failonly(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.fcmp "oeq"
+// CHECK: llvm.and
+// CHECK: }
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+// CHECK: } {fail_only}
+
+// -----
+
+// Integer min compare-capture (x > e): the compare region uses arith.cmpi sgt.
+func.func @cc_int_min(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %v: !fir.ref<i32>) {
+ %eval = fir.load %e : !fir.ref<i32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<i32> {
+ ^bb0(%xval: i32):
+ %c = arith.cmpi sgt, %xval, %eval : i32
+ %s = arith.select %c, %eval, %xval : i32
+ omp.yield(%s : i32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_int_min(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.icmp "sgt"
+// CHECK: }
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK: }
+
+// -----
+
+// Real min compare-capture (x > e): the compare region uses arith.cmpf ogt.
+func.func @cc_real_min(%x: !fir.ref<f32>, %e: !fir.ref<f32>, %v: !fir.ref<f32>) {
+ %eval = fir.load %e : !fir.ref<f32>
+ omp.atomic.capture {
+ omp.atomic.compare %x : !fir.ref<f32> {
+ ^bb0(%xval: f32):
+ %c = arith.cmpf ogt, %xval, %eval : f32
+ %s = arith.select %c, %eval, %xval : f32
+ omp.yield(%s : f32)
+ }
+ omp.atomic.read %v = %x : !fir.ref<f32>, !fir.ref<f32>, f32
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_real_min(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.fcmp "ogt"
+// CHECK: }
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, f32
+// CHECK: }
+
+// -----
+
+// Weak clause: preserved through FIR-to-LLVM conversion.
+func.func @cc_weak(%x: !fir.ref<i32>, %e: !fir.ref<i32>, %d: !fir.ref<i32>, %v: !fir.ref<i32>) {
+ %eval = fir.load %e : !fir.ref<i32>
+ %dval = fir.load %d : !fir.ref<i32>
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !fir.ref<i32>, !fir.ref<i32>, i32
+ omp.atomic.compare %x : !fir.ref<i32> {
+ ^bb0(%xval: i32):
+ %c = arith.cmpi eq, %xval, %eval : i32
+ %s = arith.select %c, %dval, %xval : i32
+ omp.yield(%s : i32)
+ } {weak}
+ }
+ return
+}
+
+// CHECK-LABEL: llvm.func @cc_weak(
+// CHECK: omp.atomic.capture {
+// CHECK: omp.atomic.read %{{.*}} = %{{.*}} : !llvm.ptr, !llvm.ptr, i32
+// CHECK: omp.atomic.compare %{{.*}} : !llvm.ptr {
+// CHECK: llvm.icmp "eq"
+// CHECK: } {weak}
+// CHECK: }
diff --git a/flang/test/Integration/OpenMP/atomic-compare-capture.f90 b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
new file mode 100644
index 0000000000000..fe97e7b12558b
--- /dev/null
+++ b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,443 @@
+!===----------------------------------------------------------------------===!
+! This directory can be used to add Integration tests involving multiple
+! stages of the compiler (for eg. from Fortran to LLVM IR). It should not
+! contain executable tests. We should only add tests here sparingly and only
+! if there is no other way to test. Repeat this message in each test that is
+! added to this directory and sub-directories.
+!===----------------------------------------------------------------------===!
+
+!RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=HLFIR
+!RUN: %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=FIR
+!RUN: %if x86-registered-target %{ %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=LLVM %}
+!RUN: %if aarch64-registered-target %{ %flang_fc1 -triple aarch64-unknown-linux-gnu -emit-llvm -fopenmp -fopenmp-version=51 %s -o - | FileCheck %s --check-prefix=LLVM %}
+
+! OpenMP atomic compare-capture tracked through every stage of the compiler
+! (HLFIR, FIR/omp dialect, LLVM IR) for every operand type: integer, real,
+! logical and complex under ==, plus integer and real under min/max (<, >).
+! Equality lowers to a cmpxchg; min/max lowers to an atomicrmw.
+
+! ===========================================================================
+! integer == (postfix): cmpxchg + select(new value) + store into v
+! HLFIR-LABEL: func.func @_QPcc_integer(
+! HLFIR: %[[X:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: }
+! HLFIR: omp.atomic.read %{{.*}} = %[[X]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_integer(
+! FIR: omp.atomic.capture memory_order(relaxed) {
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR: arith.cmpi eq
+! FIR: }
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR: }
+! LLVM-LABEL: define void @cc_integer_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM: %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM: %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM: store i32 %[[NEW]], ptr
+subroutine cc_integer(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! real == (postfix): equality on floats uses the HandleFPNegZero cmpxchg path.
+! HLFIR-LABEL: func.func @_QPcc_real(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR: arith.cmpf oeq
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_real(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR: arith.cmpf oeq
+! FIR: }
+! LLVM-LABEL: define void @cc_real_(
+! LLVM: cmpxchg ptr
+! LLVM: store float %{{.*}}, ptr
+subroutine cc_real(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! logical == (postfix): the logical atom is converted to i32, so the omp op
+! operates on !fir.ref<i32> and lowers to an i32 cmpxchg.
+! HLFIR-LABEL: func.func @_QPcc_logical(
+! HLFIR: fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! HLFIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_logical(
+! FIR: fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! LLVM-LABEL: define void @cc_logical_(
+! LLVM: cmpxchg ptr %{{.*}}, i32
+! LLVM: store i32 %{{.*}}, ptr
+subroutine cc_logical(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! complex == (postfix): fir.cmpc region; lowers to an i64 (bitcast) cmpxchg
+! and the captured new value is a { float, float } struct.
+! HLFIR-LABEL: func.func @_QPcc_complex(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! HLFIR: fir.cmpc "oeq"
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_complex(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<complex<f32>> {
+! FIR: fir.cmpc "oeq"
+! FIR: }
+! LLVM-LABEL: define void @cc_complex_(
+! LLVM: cmpxchg ptr %{{.*}}, i64
+! LLVM: store { float, float } %{{.*}}, ptr
+subroutine cc_complex(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! integer min (x > e, postfix): atomicrmw min, v gets the new value smin(old,e).
+! HLFIR-LABEL: func.func @_QPcc_integer_min(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi sgt
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_integer_min(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR: arith.cmpi sgt
+! FIR: }
+! LLVM-LABEL: define void @cc_integer_min_(
+! LLVM: %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %[[E:.*]] monotonic
+! LLVM: %[[NEW:.*]] = call i32 @llvm.smin.i32(i32 %[[OLD]], i32 %[[E]])
+! LLVM: store i32 %[[NEW]], ptr
+subroutine cc_integer_min(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! integer max (x < e, postfix): atomicrmw max, v gets the new value smax(old,e).
+! HLFIR-LABEL: func.func @_QPcc_integer_max(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi slt
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_integer_max(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR: arith.cmpi slt
+! FIR: }
+! LLVM-LABEL: define void @cc_integer_max_(
+! LLVM: %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+! LLVM: %[[NEW:.*]] = call i32 @llvm.smax.i32(i32 %[[OLD]], i32 %[[E]])
+! LLVM: store i32 %[[NEW]], ptr
+subroutine cc_integer_max(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x < e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! real min (x > e, postfix): atomicrmw fmin, v gets minnum(old, e).
+! HLFIR-LABEL: func.func @_QPcc_real_min(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR: arith.cmpf ogt
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_real_min(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR: arith.cmpf ogt
+! FIR: }
+! LLVM-LABEL: define void @cc_real_min_(
+! LLVM: %[[OLD:.*]] = atomicrmw fmin ptr %{{.*}}, float %[[E:.*]] monotonic
+! LLVM: %[[NEW:.*]] = call float @llvm.minnum.f32(float %[[OLD]], float %[[E]])
+! LLVM: store float %[[NEW]], ptr
+subroutine cc_real_min(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! real max (x < e, postfix): atomicrmw fmax, v gets maxnum(old, e).
+! HLFIR-LABEL: func.func @_QPcc_real_max(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR: arith.cmpf olt
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_real_max(
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! FIR: arith.cmpf olt
+! FIR: }
+! LLVM-LABEL: define void @cc_real_max_(
+! LLVM: %[[OLD:.*]] = atomicrmw fmax ptr %{{.*}}, float %[[E:.*]] monotonic
+! LLVM: %[[NEW:.*]] = call float @llvm.maxnum.f32(float %[[OLD]], float %[[E]])
+! LLVM: store float %[[NEW]], ptr
+subroutine cc_real_max(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x < e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! The remaining cases exercise the different *capture-statement* forms allowed
+! for atomic compare-capture (all with an integer == atom).
+
+! ===========================================================================
+! Prefix form {v = x; cond-update-stmt}: the read precedes the update, so v
+! captures the OLD value of x - the read is emitted first and v is a direct
+! store of the extracted old value (no select).
+! HLFIR-LABEL: func.func @_QPcc_prefix(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR: omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: }
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_prefix(
+! FIR: omp.atomic.capture memory_order(relaxed) {
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR: }
+! LLVM-LABEL: define void @cc_prefix_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM: store i32 %[[OLD]], ptr
+subroutine cc_prefix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end
+
+! ===========================================================================
+! Fail-only form {if(x == e){x = d;} else {v = x;}}: v is captured only when
+! the compare fails; the capture carries the fail_only attribute and lowers to
+! a conditional store of the old value.
+! HLFIR-LABEL: func.func @_QPcc_failonly(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: }
+! HLFIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR: } {fail_only}
+! FIR-LABEL: func.func @_QPcc_failonly(
+! FIR: omp.atomic.capture memory_order(relaxed) {
+! FIR: } {fail_only}
+! LLVM-LABEL: define void @cc_failonly_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM: %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM: br i1 %[[OK]], label %[[EXIT:.*]], label %[[CONT:.*]]
+! LLVM: [[CONT]]:
+! LLVM: store i32 %[[OLD]], ptr
+subroutine cc_failonly(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
+! ===========================================================================
+! Reversed comparison operand order {if(e == x){x = d;} ; v = x}: the front
+! end normalises 'e == x' to the same compare-capture as 'x == e', so this
+! accepts the alternate spelling and lowers identically (cmpxchg + select).
+! HLFIR-LABEL: func.func @_QPcc_eq_reversed(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_eq_reversed(
+! LLVM-LABEL: define void @cc_eq_reversed_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM: %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM: %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM: store i32 %[[NEW]], ptr
+subroutine cc_eq_reversed(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (e == x) x = d
+ v = x
+ !$omp end atomic
+end
+
+! ===========================================================================
+! The prefix and fail-only forms for the remaining operand types (real,
+! logical, complex), to confirm each type honours every capture form.
+
+! real, prefix: v gets the old value of x (float).
+! HLFIR-LABEL: func.func @_QPcc_real_prefix(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! HLFIR: omp.atomic.compare %[[X]]#0 : !fir.ref<f32> {
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_real_prefix(
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<f32> {
+! LLVM-LABEL: define void @cc_real_prefix_(
+! LLVM: cmpxchg ptr
+! LLVM: store float %{{.*}}, ptr
+subroutine cc_real_prefix(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end
+
+! real, fail-only: conditional store of the old value (float).
+! HLFIR-LABEL: func.func @_QPcc_real_failonly(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! HLFIR: } {fail_only}
+! FIR-LABEL: func.func @_QPcc_real_failonly(
+! FIR: } {fail_only}
+! LLVM-LABEL: define void @cc_real_failonly_(
+! LLVM: cmpxchg ptr
+! LLVM: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}
+! LLVM: store float %{{.*}}, ptr
+subroutine cc_real_failonly(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
+! logical, prefix: v gets the old value (via the i32 atom).
+! HLFIR-LABEL: func.func @_QPcc_logical_prefix(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR-LABEL: func.func @_QPcc_logical_prefix(
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! LLVM-LABEL: define void @cc_logical_prefix_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+subroutine cc_logical_prefix(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x .eqv. e) x = d
+ !$omp end atomic
+end
+
+! logical, fail-only: conditional store (via the i32 atom).
+! HLFIR-LABEL: func.func @_QPcc_logical_failonly(
+! HLFIR: } {fail_only}
+! FIR-LABEL: func.func @_QPcc_logical_failonly(
+! FIR: } {fail_only}
+! LLVM-LABEL: define void @cc_logical_failonly_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i32
+! LLVM: %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM: br i1 %[[OK]], label %{{.*}}, label %{{.*}}
+subroutine cc_logical_failonly(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
+! complex, prefix: v gets the old complex value (struct), no select.
+! HLFIR-LABEL: func.func @_QPcc_complex_prefix(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! HLFIR: omp.atomic.compare %[[X]]#0 : !fir.ref<complex<f32>> {
+! FIR-LABEL: func.func @_QPcc_complex_prefix(
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! LLVM-LABEL: define void @cc_complex_prefix_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
+! LLVM: store { float, float } %{{.*}}, ptr
+subroutine cc_complex_prefix(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end
+
+! complex, fail-only: conditional store of the old complex value.
+! HLFIR-LABEL: func.func @_QPcc_complex_failonly(
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! HLFIR: } {fail_only}
+! FIR-LABEL: func.func @_QPcc_complex_failonly(
+! FIR: } {fail_only}
+! LLVM-LABEL: define void @cc_complex_failonly_(
+! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
+! LLVM: %[[OK:.*]] = extractvalue { i64, i1 } %[[RES]], 1
+! LLVM: %[[FAILED:.*]] = xor i1 %[[OK]], true
+! LLVM: br i1 %[[FAILED]], label %{{.*}}, label %{{.*}}
+! LLVM: store { float, float } %{{.*}}, ptr
+subroutine cc_complex_failonly(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
+! ===========================================================================
+! weak clause (postfix): cmpxchg gets the 'weak' modifier.
+! HLFIR-LABEL: func.func @_QPcc_weak(
+! HLFIR: omp.atomic.capture memory_order(relaxed) {
+! HLFIR: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! HLFIR: arith.cmpi eq
+! HLFIR: } {weak}
+! HLFIR: omp.atomic.read %{{.*}} = %{{.*}}#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! HLFIR: }
+! FIR-LABEL: func.func @_QPcc_weak(
+! FIR: omp.atomic.capture memory_order(relaxed) {
+! FIR: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! FIR: arith.cmpi eq
+! FIR: } {weak}
+! FIR: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! FIR: }
+! LLVM-LABEL: define void @cc_weak_(
+! LLVM: %[[RES:.*]] = cmpxchg weak ptr %{{.*}}, i32 %{{.*}}, i32 %[[D:.*]] monotonic monotonic
+! LLVM: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RES]], 0
+! LLVM: %[[OK:.*]] = extractvalue { i32, i1 } %[[RES]], 1
+! LLVM: %[[NEW:.*]] = select i1 %[[OK]], i32 %[[D]], i32 %[[OLD]]
+! LLVM: store i32 %[[NEW]], ptr
+subroutine cc_weak(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture weak
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
diff --git a/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90 b/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
new file mode 100644
index 0000000000000..ce6f0a3d6454b
--- /dev/null
+++ b/flang/test/Lower/OpenMP/Todo/atomic-compare-capture-result.f90
@@ -0,0 +1,20 @@
+! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! The comparison-result form of atomic compare captures the boolean outcome of
+! the comparison into `r`:
+! r = x == e
+! if (r) x = d
+! This is accepted by semantics, but the atomic analysis does not record the
+! assignment to `r`, so lowering would silently drop it and never write `r`.
+! Ensure it is diagnosed as "not yet implemented" instead of producing code that
+! leaves `r` unwritten.
+
+! CHECK: not yet implemented: atomic compare capturing the comparison result
+subroutine f(x, e, d, r)
+ integer :: x, e, d
+ logical :: r
+ !$omp atomic compare
+ r = x == e
+ if (r) x = d
+ !$omp end atomic
+end subroutine
diff --git a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90 b/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
index 27c7b1fb1a432..7165bdb2fc122 100644
--- a/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
+++ b/flang/test/Lower/OpenMP/Todo/atomic-compare-fail.f90
@@ -5,20 +5,12 @@ program p
integer :: x
integer :: r
integer :: d
- integer :: v
!$omp atomic compare fail(relaxed)
if (x .eq. 0) then
x = 2
end if
!$omp end atomic
- !$omp atomic compare capture
- v = x
- if (x > r) then
- x = d
- end if
- !$omp end atomic
-
!$omp atomic compare fail(relaxed)
if (x > r) then
x = d
diff --git a/flang/test/Lower/OpenMP/atomic-compare-capture.f90 b/flang/test/Lower/OpenMP/atomic-compare-capture.f90
new file mode 100644
index 0000000000000..93fbf2d4bd034
--- /dev/null
+++ b/flang/test/Lower/OpenMP/atomic-compare-capture.f90
@@ -0,0 +1,356 @@
+! This test checks lowering of atomic compare capture constructs to HLFIR.
+! RUN: bbc %openmp_flags -fopenmp-version=51 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir %openmp_flags -fopenmp-version=51 %s -o - | FileCheck %s
+
+! ---------------------------------------------------------------------------
+! integer == postfix: { if (x==e) x=d; v=x }
+! CHECK-LABEL: func.func @_QPcc_int_postfix(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[D:.*]]: !fir.ref<i32> {fir.bindc_name = "d"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[D_DECL:.*]]:2 = hlfir.declare %[[D]] {{.*}}
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[D_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi eq, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_int_postfix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == prefix: { v=x; if (x==e) x=d }
+! CHECK-LABEL: func.func @_QPcc_int_prefix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %{{.*}}#0 = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: omp.yield
+! CHECK: }
+! CHECK: }
+subroutine cc_int_prefix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == fail-only: { if (x==e) x=d; else v=x }
+! CHECK-LABEL: func.func @_QPcc_int_failonly(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: arith.cmpi eq, %[[XVAL]], %{{.*}} : i32
+! CHECK: omp.yield
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}}#0 = %{{.*}}#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: } {fail_only}
+subroutine cc_int_failonly(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer == reversed (e==x): same lowering as x==e
+! CHECK-LABEL: func.func @_QPcc_int_eq_reversed(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_int_eq_reversed(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture
+ if (e == x) x = d
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == postfix
+! CHECK-LABEL: func.func @_QPcc_real_postfix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK: arith.cmpf oeq
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine cc_real_postfix(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == prefix
+! CHECK-LABEL: func.func @_QPcc_real_prefix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: omp.atomic.compare %[[X]]#0 : !fir.ref<f32> {
+! CHECK: arith.cmpf oeq
+! CHECK: }
+! CHECK: }
+subroutine cc_real_prefix(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real == fail-only
+! CHECK-LABEL: func.func @_QPcc_real_failonly(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK: arith.cmpf oeq
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: } {fail_only}
+subroutine cc_real_failonly(x, e, d, v)
+ real :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. postfix
+! CHECK-LABEL: func.func @_QPcc_logical_postfix(
+! CHECK: fir.convert %{{.*}} : (!fir.logical<4>) -> i32
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_logical_postfix(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) x = d
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. prefix
+! CHECK-LABEL: func.func @_QPcc_logical_prefix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: }
+! CHECK: }
+subroutine cc_logical_prefix(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x .eqv. e) x = d
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! logical .eqv. fail-only
+! CHECK-LABEL: func.func @_QPcc_logical_failonly(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}} : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: } {fail_only}
+subroutine cc_logical_failonly(x, e, d, v)
+ logical :: x, e, d, v
+ !$omp atomic compare capture
+ if (x .eqv. e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == postfix
+! CHECK-LABEL: func.func @_QPcc_complex_postfix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! CHECK: fir.cmpc "oeq"
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK: }
+subroutine cc_complex_postfix(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == prefix
+! CHECK-LABEL: func.func @_QPcc_complex_prefix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %{{.*}} = %[[X:.*]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK: omp.atomic.compare %[[X]]#0 : !fir.ref<complex<f32>> {
+! CHECK: fir.cmpc "oeq"
+! CHECK: }
+! CHECK: }
+subroutine cc_complex_prefix(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! complex == fail-only
+! CHECK-LABEL: func.func @_QPcc_complex_failonly(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<complex<f32>> {
+! CHECK: fir.cmpc "oeq"
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK: } {fail_only}
+subroutine cc_complex_failonly(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) then
+ x = d
+ else
+ v = x
+ end if
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer min (x > e) postfix
+! CHECK-LABEL: func.func @_QPcc_int_min(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK: arith.cmpi sgt
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_int_min(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! integer max (x < e) postfix
+! CHECK-LABEL: func.func @_QPcc_int_max(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK: arith.cmpi slt
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_int_max(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x < e) x = e
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real min (x > e) postfix
+! CHECK-LABEL: func.func @_QPcc_real_min(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK: arith.cmpf ogt
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine cc_real_min(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! real max (x < e) postfix
+! CHECK-LABEL: func.func @_QPcc_real_max(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<f32> {
+! CHECK: arith.cmpf olt
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine cc_real_max(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x < e) x = e
+ v = x
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! weak clause (prefix form)
+! CHECK-LABEL: func.func @_QPcc_weak_prefix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.read %{{.*}}#0 = %[[X:.*]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: omp.atomic.compare %[[X]]#0 : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: } {weak}
+! CHECK: }
+subroutine cc_weak_prefix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture weak
+ v = x
+ if (x == e) x = d
+ !$omp end atomic
+end subroutine
+
+! ---------------------------------------------------------------------------
+! weak clause (postfix form)
+! CHECK-LABEL: func.func @_QPcc_weak_postfix(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %{{.*}}#0 : !fir.ref<i32> {
+! CHECK: arith.cmpi eq
+! CHECK: } {weak}
+! CHECK: omp.atomic.read %{{.*}} : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine cc_weak_postfix(x, e, d, v)
+ integer :: x, e, d, v
+ !$omp atomic compare capture weak
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end subroutine
diff --git a/flang/test/Lower/OpenMP/atomic-compare.f90 b/flang/test/Lower/OpenMP/atomic-compare.f90
index ccc44a370be79..83e6f81ca00e3 100644
--- a/flang/test/Lower/OpenMP/atomic-compare.f90
+++ b/flang/test/Lower/OpenMP/atomic-compare.f90
@@ -218,6 +218,101 @@ subroutine atomic_compare_capture_int_gt(x, e, d, v)
!$omp end atomic
end
+! Min/max (postfix) compare-capture: update first, capture the new value.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_min_postfix(
+! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
+! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
+! CHECK-SAME: %[[V:.*]]: !fir.ref<i32> {fir.bindc_name = "v"})
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %[[E]] {{.*}}
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %[[V]] {{.*}}
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %[[X]] {{.*}}
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<i32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %[[EVAL]] : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: }
+subroutine atomic_compare_capture_min_postfix(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! Min/max fail-only compare-capture: v is captured in the else branch (when the
+! comparison, and hence the update, does not happen).
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_min_failonly(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<i32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: i32):
+! CHECK: %[[CMP:.*]] = arith.cmpi sgt, %[[XVAL]], %{{.*}} : i32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : i32
+! CHECK: omp.yield(%[[SEL]] : i32)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<i32>, !fir.ref<i32>, i32
+! CHECK: } {fail_only}
+subroutine atomic_compare_capture_min_failonly(x, e, v)
+ integer :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) then
+ x = e
+ else
+ v = x
+ end if
+ !$omp end atomic
+end
+
+! REAL min (postfix): if (x > e) x = e ; v = x -> compare region uses arith.cmpf.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real_min(
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Ee"
+! CHECK: %[[V_DECL:.*]]:2 = hlfir.declare %{{.*}}Ev"
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! CHECK: %[[EVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: %[[DVAL:.*]] = fir.load %[[E_DECL]]#0 : !fir.ref<f32>
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: f32):
+! CHECK: %[[CMP:.*]] = arith.cmpf ogt, %[[XVAL]], %[[EVAL]] fastmath<contract> : f32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %[[DVAL]], %[[XVAL]] : f32
+! CHECK: omp.yield(%[[SEL]] : f32)
+! CHECK: }
+! CHECK: omp.atomic.read %[[V_DECL]]#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine atomic_compare_capture_real_min(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x > e) x = e
+ v = x
+ !$omp end atomic
+end
+
+! REAL max (postfix): if (x < e) x = e ; v = x.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_real_max(
+! CHECK: %[[E_DECL:.*]]:2 = hlfir.declare %{{.*}}Ee"
+! CHECK: %[[X_DECL:.*]]:2 = hlfir.declare %{{.*}}Ex"
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL]]#0 : !fir.ref<f32> {
+! CHECK: ^bb0(%[[XVAL:.*]]: f32):
+! CHECK: %[[CMP:.*]] = arith.cmpf olt, %[[XVAL]], %{{.*}} fastmath<contract> : f32
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : f32
+! CHECK: omp.yield(%[[SEL]] : f32)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<f32>, !fir.ref<f32>, f32
+! CHECK: }
+subroutine atomic_compare_capture_real_max(x, e, v)
+ real :: x, e, v
+ !$omp atomic compare capture
+ if (x < e) x = e
+ v = x
+ !$omp end atomic
+end
+
! CHECK-LABEL: func.func @_QPatomic_compare_capture_postfix(
! CHECK-SAME: %[[X:.*]]: !fir.ref<i32> {fir.bindc_name = "x"},
! CHECK-SAME: %[[E:.*]]: !fir.ref<i32> {fir.bindc_name = "e"},
@@ -305,6 +400,27 @@ subroutine atomic_compare_capture_real(x, e, d, v)
!$omp end atomic
end
+! Complex compare-capture (postfix form): the compare region decomposes into
+! per-field comparisons combined with fir.cmpc/arith and the desired value is
+! selected as a whole complex value.
+! CHECK-LABEL: func.func @_QPatomic_compare_capture_complex(
+! CHECK: omp.atomic.capture memory_order(relaxed) {
+! CHECK: omp.atomic.compare %[[X_DECL:.*]]#0 : !fir.ref<complex<f32>> {
+! CHECK: ^bb0(%[[XVAL:.*]]: complex<f32>):
+! CHECK: %[[CMP:.*]] = fir.cmpc "oeq", %[[XVAL]], %{{.*}} : complex<f32>
+! CHECK: %[[SEL:.*]] = arith.select %[[CMP]], %{{.*}}, %[[XVAL]] : complex<f32>
+! CHECK: omp.yield(%[[SEL]] : complex<f32>)
+! CHECK: }
+! CHECK: omp.atomic.read %{{.*}}#0 = %[[X_DECL]]#0 : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
+! CHECK: }
+subroutine atomic_compare_capture_complex(x, e, d, v)
+ complex :: x, e, d, v
+ !$omp atomic compare capture
+ if (x == e) x = d
+ v = x
+ !$omp end atomic
+end
+
! Compare-capture with an explicit seq_cst memory order (prefix read form).
! CHECK-LABEL: func.func @_QPatomic_compare_capture_seq_cst(
! CHECK: omp.atomic.capture memory_order(seq_cst) {
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 8e49571cd2461..060e563ecefa8 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5370,6 +5370,99 @@ convertFCmpPredicateToAtomicCompareOp(LLVM::FCmpPredicate predicate) {
}
}
+/// Result of matching the decomposed complex equality pattern inside an atomic
+/// compare region.
+struct ComplexComparePattern {
+ bool isComplex = false;
+ bool isNE = false; // `or` of the field compares => NE (unsupported).
+ mlir::Value eAggregate; // The complex expected value (`e`).
+ bool isXBinopExpr = false; // True if x is the first fcmp operand.
+};
+
+/// Detect a decomposed complex equality comparison in an atomic compare region:
+/// %re_x = llvm.extractvalue %xval[0]
+/// %re_e = llvm.extractvalue %eStruct[0]
+/// %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
+/// %im_x = llvm.extractvalue %xval[1]
+/// %im_e = llvm.extractvalue %eStruct[1]
+/// %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
+/// %cmp = llvm.and %cmp_re, %cmp_im (llvm.or would be NE)
+/// It is recognised by an and/or whose operands are both fcmps operating on
+/// extractvalues, one chain rooted at the block argument (x) and the other at
+/// the expected complex value (e).
+static ComplexComparePattern detectComplexCompareEq(Block &block) {
+ ComplexComparePattern result;
+ auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
+ if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
+ return extractOp.getContainer();
+ return nullptr;
+ };
+ for (Operation &op : block.getOperations()) {
+ if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
+ continue;
+ auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
+ auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
+ if (!lhsFcmp || !rhsFcmp)
+ continue;
+ mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
+ mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
+ bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
+ bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
+ if (!lhsXIsOp0 && !lhsXIsOp1)
+ continue;
+ mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
+ if (!eAggregate)
+ continue;
+ result.isComplex = true;
+ result.isNE = isa<LLVM::OrOp>(op);
+ result.eAggregate = eAggregate;
+ result.isXBinopExpr = lhsXIsOp0;
+ break;
+ }
+ return result;
+}
+
+/// Emit a bitcast-to-integer `cmpxchg` for a complex (struct-typed) atomic
+/// compare. The expected/desired complex values are spilled to allocas and
+/// reloaded as an integer of the same width, then compared and swapped. Returns
+/// the cmpxchg (result type `{iN, i1}`); `maxAlign` receives the alignment used
+/// so callers can reinterpret the captured old value through memory.
+static llvm::AtomicCmpXchgInst *
+emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
+ llvm::Type *complexTy, llvm::Value *eVal,
+ llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering,
+ bool isWeak, llvm::Align &maxAlign) {
+ const llvm::DataLayout &DL =
+ builder.GetInsertBlock()->getModule()->getDataLayout();
+ unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
+ llvm::IntegerType *intTy =
+ llvm::IntegerType::get(builder.getContext(), totalBits);
+ llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
+ llvm::Align intAlign = DL.getABITypeAlign(intTy);
+ maxAlign = std::max(complexAlign, intAlign);
+
+ llvm::AllocaInst *eAlloca =
+ builder.CreateAlloca(complexTy, nullptr, "cmplx.e");
+ eAlloca->setAlignment(maxAlign);
+ llvm::AllocaInst *dAlloca =
+ builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
+ dAlloca->setAlignment(maxAlign);
+
+ builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
+ llvm::Value *eInt =
+ builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
+ builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
+ llvm::Value *dInt =
+ builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
+
+ llvm::AtomicOrdering failOrdering =
+ llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
+ auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
+ atomicOrdering, failOrdering);
+ cmpXchg->setWeak(isWeak);
+ return cmpXchg;
+}
+
/// Holds the extracted comparison pattern information from an atomic compare
/// region.
struct AtomicComparePatternInfo {
@@ -5379,7 +5472,6 @@ struct AtomicComparePatternInfo {
bool isXBinopExpr = false;
bool isSigned = false;
};
-
/// Extract comparison predicate, expected value (e), desired value (d), and
/// related flags from an atomic compare region block by scanning for
/// icmp/fcmp/select/min/max operations.
@@ -5387,6 +5479,27 @@ static LogicalResult extractAtomicComparePattern(
Block &block,
llvm::function_ref<llvm::Value *(mlir::Value)> materializeValue,
omp::AtomicCompareOp atomicCompareOp, AtomicComparePatternInfo &info) {
+ // Complex equality is a decomposed per-field pattern (extractvalue + fcmp +
+ // and) rather than a single scalar compare. Detect it first so the scalar
+ // icmp/fcmp handling below does not mistake a real/imaginary field for the
+ // whole expected value.
+ if (ComplexComparePattern cplx = detectComplexCompareEq(block);
+ cplx.isComplex) {
+ if (cplx.isNE)
+ return atomicCompareOp.emitError(
+ "unsupported comparison predicate (NE) for complex atomic compare");
+ info.compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+ info.isXBinopExpr = cplx.isXBinopExpr;
+ info.eVal = materializeValue(cplx.eAggregate);
+ for (Operation &op : block.getOperations()) {
+ if (auto selectOp = dyn_cast<LLVM::SelectOp>(op)) {
+ info.dVal = materializeValue(selectOp.getTrueValue());
+ break;
+ }
+ }
+ return success();
+ }
+
for (Operation &op : block.getOperations()) {
// Pre-filter: skip icmps that don't involve the block argument
// (e.g., truthiness extractions from logical-to-integer conversion).
@@ -5573,22 +5686,101 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
bool isPostfixCapture = !isReadFirst;
bool isFailOnly = atomicCaptureOp.getFailOnly();
+ // Complex equality capture: x is struct-typed, which the OMPIRBuilder
+ // cannot handle, so emit a bitcast-to-integer cmpxchg (as in the
+ // non-capture complex path) and reconstruct the captured value from its
+ // result through memory. Complex only supports the == comparison.
+ if (llvmXElementType->isStructTy()) {
+ llvm::Align maxAlign;
+ llvm::AtomicCmpXchgInst *cmpXchg = emitComplexAtomicCmpXchg(
+ builder, llvmX, llvmXElementType, eVal, dVal, atomicOrdering,
+ atomicCompareOp.getWeak(), maxAlign);
+
+ llvm::Value *oldInt = builder.CreateExtractValue(cmpXchg, 0);
+ llvm::Value *cmpOk = builder.CreateExtractValue(cmpXchg, 1);
+
+ // Reinterpret the old integer value as the complex struct via memory.
+ llvm::AllocaInst *oldAlloca =
+ builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.old");
+ oldAlloca->setAlignment(maxAlign);
+ builder.CreateAlignedStore(oldInt, oldAlloca, maxAlign);
+ llvm::Value *oldComplex = builder.CreateAlignedLoad(
+ llvmXElementType, oldAlloca, maxAlign, "cmplx.old.val");
+
+ if (isFailOnly) {
+ // v is written only when the compare fails (cmpOk == false).
+ llvm::Value *cmpFailed = builder.CreateNot(cmpOk);
+ llvm::BasicBlock *curBB = builder.GetInsertBlock();
+ llvm::Function *fn = curBB->getParent();
+ llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
+ builder.getContext(), "omp.atomic.cont", fn);
+ llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
+ builder.getContext(), "omp.atomic.exit", fn);
+ builder.CreateCondBr(cmpFailed, contBB, exitBB);
+ builder.SetInsertPoint(contBB);
+ builder.CreateStore(oldComplex, llvmAtomicV.Var,
+ llvmAtomicV.IsVolatile);
+ builder.CreateBr(exitBB);
+ builder.SetInsertPoint(exitBB);
+ } else if (isPostfixCapture) {
+ // v gets the new value of x: d on success, old x otherwise.
+ llvm::Value *newComplex = builder.CreateSelect(cmpOk, dVal, oldComplex);
+ builder.CreateStore(newComplex, llvmAtomicV.Var,
+ llvmAtomicV.IsVolatile);
+ } else {
+ // Prefix: v gets the old value of x.
+ builder.CreateStore(oldComplex, llvmAtomicV.Var,
+ llvmAtomicV.IsVolatile);
+ }
+
+ // Emit flush after atomic compare if needed (release/acq_rel/seq_cst).
+ if (atomicOrdering == llvm::AtomicOrdering::Release ||
+ atomicOrdering == llvm::AtomicOrdering::AcquireRelease ||
+ atomicOrdering == llvm::AtomicOrdering::SequentiallyConsistent) {
+ llvm::OpenMPIRBuilder::LocationDescription flushLoc(builder);
+ ompBuilder->createFlush(flushLoc);
+ }
+ return success();
+ }
+
+ // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
+ // notion of a failed compare for an atomicrmw, so the fail-only capture
+ // form (v written only when the compare fails) has no valid mapping and is
+ // Min/max (<, >) comparisons lower to an atomicrmw. The OMPIRBuilder has no
+ // notion of a failed compare for an atomicrmw (it asserts on IsFailOnly),
+ // so for min/max the fail-only capture is reconstructed manually below.
+ bool isMinMax = compareOp != llvm::omp::OMPAtomicCompareOp::EQ;
+
llvm::OpenMPIRBuilder::AtomicOpValue llvmAtomicVForCall = llvmAtomicV;
- // Postfix: bypass V in OMPIRBuilder, handled manually below.
- if (isPostfixCapture && !isFailOnly)
+ // Capture into V is reconstructed manually below for:
+ // * postfix capture (v gets the new value of x): equality from the
+ // cmpxchg result, min/max from the atomicrmw result;
+ // * min/max fail-only capture (the OMPIRBuilder cannot express it).
+ // Bypass V in the OMPIRBuilder for those cases so it does not also emit its
+ // own (for min/max, incorrect or unsupported) capture store.
+ bool minMaxManualCapture = isMinMax && (isPostfixCapture || isFailOnly);
+ bool eqPostfixManualCapture = !isMinMax && isPostfixCapture && !isFailOnly;
+ if (minMaxManualCapture || eqPostfixManualCapture)
llvmAtomicVForCall = {nullptr, nullptr, false, false};
- // Prefix: IsPostfixUpdate=true → direct store of old value.
- // Fail-only: IsPostfixUpdate=false → conditional store on failure.
- bool isPostfixUpdate = !isFailOnly;
+ // The OMPIRBuilder only understands IsFailOnly for the equality (cmpxchg)
+ // path; for min/max it would assert. Min/max fail-only is handled here.
+ bool builderFailOnly = isFailOnly && !isMinMax;
+
+ // IsPostfixUpdate selects which value the OMPIRBuilder captures into V:
+ // * min/max prefix and equality prefix: a direct store of the old value
+ // (IsPostfixUpdate=true).
+ // * equality fail-only: a conditional store (IsPostfixUpdate=false).
+ // Manually-reconstructed captures bypass V above.
+ bool isPostfixUpdate = !builderFailOnly;
bool isWeak = atomicCompareOp.getWeak();
bool savedHandleFPNegZero = ompBuilder->setHandleFPNegZero(true);
llvm::OpenMPIRBuilder::InsertPointOrErrorTy afterIP =
- ompBuilder->createAtomicCompare(ompLoc, llvmAtomicX, llvmAtomicVForCall,
- llvmAtomicR, eVal, dVal, atomicOrdering,
- compareOp, isXBinopExpr,
- isPostfixUpdate, isFailOnly, isWeak);
+ ompBuilder->createAtomicCompare(
+ ompLoc, llvmAtomicX, llvmAtomicVForCall, llvmAtomicR, eVal, dVal,
+ atomicOrdering, compareOp, isXBinopExpr, isPostfixUpdate,
+ builderFailOnly, isWeak);
ompBuilder->setHandleFPNegZero(savedHandleFPNegZero);
if (failed(handleError(afterIP, *atomicCaptureOp)))
@@ -5596,8 +5788,99 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
builder.restoreIP(*afterIP);
- // Postfix: v = select(success, D, old) — captures new value of x.
- if (isPostfixCapture && !isFailOnly) {
+ // Min/max capture is reconstructed from the atomicrmw the OMPIRBuilder
+ // emits (its result is the old value of x). V was bypassed above.
+ // * postfix: v gets the new value min/max(old, e);
+ // * fail-only: v gets the old value, but only when the compare failed
+ // (i.e. the atomicrmw did not change x).
+ // (Prefix min/max captures the old value directly through V, so nothing
+ // extra is needed there.)
+ if (isMinMax && (isPostfixCapture || isFailOnly)) {
+ llvm::BasicBlock *curBB = builder.GetInsertBlock();
+ llvm::AtomicRMWInst *rmw = nullptr;
+ for (auto &inst : llvm::reverse(*curBB)) {
+ if (auto *r = dyn_cast<llvm::AtomicRMWInst>(&inst)) {
+ rmw = r;
+ break;
+ }
+ }
+ assert(rmw && "expected atomicrmw for min/max compare capture");
+ llvm::Value *oldVal = rmw;
+ llvm::Value *rhs = rmw->getValOperand();
+
+ if (isFailOnly) {
+ // The compare "failed" (the else branch runs) exactly when the
+ // atomicrmw did not change x. Recompute the original update condition
+ // on the old value and negate it. v is stored only in that case.
+ llvm::CmpInst::Predicate updatePred;
+ switch (rmw->getOperation()) {
+ case llvm::AtomicRMWInst::Min:
+ updatePred = llvm::CmpInst::ICMP_SGT;
+ break;
+ case llvm::AtomicRMWInst::Max:
+ updatePred = llvm::CmpInst::ICMP_SLT;
+ break;
+ case llvm::AtomicRMWInst::UMin:
+ updatePred = llvm::CmpInst::ICMP_UGT;
+ break;
+ case llvm::AtomicRMWInst::UMax:
+ updatePred = llvm::CmpInst::ICMP_ULT;
+ break;
+ case llvm::AtomicRMWInst::FMin:
+ updatePred = llvm::CmpInst::FCMP_OGT;
+ break;
+ case llvm::AtomicRMWInst::FMax:
+ updatePred = llvm::CmpInst::FCMP_OLT;
+ break;
+ default:
+ llvm_unreachable(
+ "unexpected atomicrmw op for min/max compare capture");
+ }
+ llvm::Value *updated = builder.CreateCmp(updatePred, oldVal, rhs);
+ llvm::Value *failed = builder.CreateNot(updated);
+ llvm::Function *fn = curBB->getParent();
+ llvm::BasicBlock *contBB = llvm::BasicBlock::Create(
+ builder.getContext(), "omp.atomic.cont", fn);
+ llvm::BasicBlock *exitBB = llvm::BasicBlock::Create(
+ builder.getContext(), "omp.atomic.exit", fn);
+ builder.CreateCondBr(failed, contBB, exitBB);
+ builder.SetInsertPoint(contBB);
+ builder.CreateStore(oldVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+ builder.CreateBr(exitBB);
+ builder.SetInsertPoint(exitBB);
+ } else {
+ llvm::Intrinsic::ID id;
+ switch (rmw->getOperation()) {
+ case llvm::AtomicRMWInst::Min:
+ id = llvm::Intrinsic::smin;
+ break;
+ case llvm::AtomicRMWInst::Max:
+ id = llvm::Intrinsic::smax;
+ break;
+ case llvm::AtomicRMWInst::UMin:
+ id = llvm::Intrinsic::umin;
+ break;
+ case llvm::AtomicRMWInst::UMax:
+ id = llvm::Intrinsic::umax;
+ break;
+ case llvm::AtomicRMWInst::FMin:
+ id = llvm::Intrinsic::minnum;
+ break;
+ case llvm::AtomicRMWInst::FMax:
+ id = llvm::Intrinsic::maxnum;
+ break;
+ default:
+ llvm_unreachable(
+ "unexpected atomicrmw op for min/max compare capture");
+ }
+ llvm::Value *newVal = builder.CreateBinaryIntrinsic(id, oldVal, rhs);
+ builder.CreateStore(newVal, llvmAtomicV.Var, llvmAtomicV.IsVolatile);
+ }
+ }
+
+ // Equality postfix: v = select(success, D, old) — reconstructs the new
+ // value of x from the cmpxchg result.
+ if (!isMinMax && isPostfixCapture && !isFailOnly) {
llvm::BasicBlock *curBB = builder.GetInsertBlock();
llvm::Value *oldVal = nullptr;
llvm::Value *successVal = nullptr;
@@ -5848,56 +6131,18 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
llvm::Value *dVal = nullptr;
bool isXBinopExpr = false;
- auto traceToAggregate = [](mlir::Value v) -> mlir::Value {
- if (auto extractOp = v.getDefiningOp<LLVM::ExtractValueOp>())
- return extractOp.getContainer();
- return nullptr;
- };
-
- // Check for a decomposed complex comparison pattern:
- // %re_x = llvm.extractvalue %xval[0]
- // %re_e = llvm.extractvalue %eStruct[0]
- // %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
- // %im_x = llvm.extractvalue %xval[1]
- // %im_e = llvm.extractvalue %eStruct[1]
- // %cmp_im = llvm.fcmp "oeq" %im_x, %im_e
- // %cmp = llvm.and %cmp_re, %cmp_im (for EQ)
- // Detect this by looking for AndOp/OrOp whose operands are both FCmpOps
- // operating on ExtractValueOps from the block argument.
- bool isComplexPattern = false;
- for (Operation &op : block.getOperations()) {
- if (!isa<LLVM::AndOp, LLVM::OrOp>(op))
- continue;
-
- // Using : %cmp = llvm.and %cmp_re, %cmp_im
- auto lhsFcmp = op.getOperand(0).getDefiningOp<LLVM::FCmpOp>();
- auto rhsFcmp = op.getOperand(1).getDefiningOp<LLVM::FCmpOp>();
- if (!lhsFcmp || !rhsFcmp)
- continue;
-
- // Using : %cmp_re = llvm.fcmp "oeq" %re_x, %re_e
- // Check presence of x (block argument) and get e.
- mlir::Value lhsAgg0 = traceToAggregate(lhsFcmp.getOperand(0));
- mlir::Value lhsAgg1 = traceToAggregate(lhsFcmp.getOperand(1));
- bool lhsXIsOp0 = (lhsAgg0 == block.getArgument(0));
- bool lhsXIsOp1 = (lhsAgg1 == block.getArgument(0));
- if (!lhsXIsOp0 && !lhsXIsOp1)
- continue;
- mlir::Value eAggregate = lhsXIsOp0 ? lhsAgg1 : lhsAgg0;
- if (!eAggregate)
- continue;
-
- if (isa<LLVM::AndOp>(op))
- compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
- else
+ // Check for a decomposed complex comparison pattern (extractvalue + fcmp +
+ // and/or of the real/imaginary fields).
+ ComplexComparePattern cplx = detectComplexCompareEq(block);
+ bool isComplexPattern = cplx.isComplex;
+ if (isComplexPattern) {
+ if (cplx.isNE)
// OrOp corresponds to NE, which is not a valid atomic compare op.
return atomicCompareOp.emitError(
"unsupported comparison predicate (NE) for complex atomic compare");
-
- isXBinopExpr = lhsXIsOp0;
- eVal = materializeValue(eAggregate);
- isComplexPattern = true;
- break;
+ compareOp = llvm::omp::OMPAtomicCompareOp::EQ;
+ isXBinopExpr = cplx.isXBinopExpr;
+ eVal = materializeValue(cplx.eAggregate);
}
if (isComplexPattern) {
@@ -5916,37 +6161,10 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
dVal = materializeValue(yieldOp.getResults()[0]);
}
- const llvm::DataLayout &DL =
- builder.GetInsertBlock()->getModule()->getDataLayout();
- unsigned totalBits =
- DL.getTypeStoreSizeInBits(llvmXElementType).getFixedValue();
-
- llvm::IntegerType *intTy =
- llvm::IntegerType::get(builder.getContext(), totalBits);
-
- llvm::Align complexAlign = DL.getABITypeAlign(llvmXElementType);
- llvm::Align intAlign = DL.getABITypeAlign(intTy);
- llvm::Align maxAlign = std::max(complexAlign, intAlign);
-
- llvm::AllocaInst *eAlloca =
- builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.e");
- eAlloca->setAlignment(maxAlign);
- llvm::AllocaInst *dAlloca =
- builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.d");
- dAlloca->setAlignment(maxAlign);
-
- builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
- llvm::Value *eInt =
- builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
- builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
- llvm::Value *dInt =
- builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
-
- llvm::AtomicOrdering failOrdering =
- llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
- auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
- atomicOrdering, failOrdering);
- cmpXchg->setWeak(atomicCompareOp.getWeak());
+ llvm::Align maxAlign;
+ emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+ atomicOrdering, atomicCompareOp.getWeak(),
+ maxAlign);
// Emit flush after atomic compare if needed (for release, acq_rel,
// seq_cst orderings).
diff --git a/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
new file mode 100644
index 0000000000000..2a3cb6dc99133
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/openmp-atomic-compare-capture.mlir
@@ -0,0 +1,407 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// Prefix compare+capture: {read, compare}
+// CHECK-LABEL: define void @compare_capture_prefix(
+// CHECK-SAME: ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK: %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK: store i32 %[[OLD]], ptr %[[V]], align 4
+llvm.func @compare_capture_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> i32
+ %dval = llvm.load %d : !llvm.ptr -> i32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: i32):
+ %cmp = llvm.icmp "eq" %xval, %eval : i32
+ %sel = llvm.select %cmp, %dval, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ }
+ llvm.return
+}
+
+// Postfix compare+capture: {compare, read}
+// CHECK-LABEL: define void @compare_capture_postfix(
+// CHECK-SAME: ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK: %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RESULT]], 1
+// CHECK: %[[NEWVAL:.*]] = select i1 %[[SUCCESS]], i32 %[[DVAL]], i32 %[[OLD]]
+// CHECK: store i32 %[[NEWVAL]], ptr %[[V]], align 4
+llvm.func @compare_capture_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> i32
+ %dval = llvm.load %d : !llvm.ptr -> i32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: i32):
+ %cmp = llvm.icmp "eq" %xval, %eval : i32
+ %sel = llvm.select %cmp, %dval, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ }
+ llvm.return
+}
+
+// Fail-only compare+capture: {compare, read} with fail_only
+// CHECK-LABEL: define void @compare_capture_failonly(
+// CHECK-SAME: ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK: %[[RESULT:.*]] = cmpxchg ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK: %[[SUCCESS:.*]] = extractvalue { i32, i1 } %[[RESULT]], 1
+// CHECK: br i1 %[[SUCCESS]], label %{{.*}}.atomic.exit, label %{{.*}}.atomic.cont
+// CHECK: {{.*}}.atomic.cont:
+// CHECK: store i32 %[[OLD]], ptr %[[V]]
+// CHECK: br label %{{.*}}.atomic.exit
+// CHECK: {{.*}}.atomic.exit:
+llvm.func @compare_capture_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> i32
+ %dval = llvm.load %d : !llvm.ptr -> i32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: i32):
+ %cmp = llvm.icmp "eq" %xval, %eval : i32
+ %sel = llvm.select %cmp, %dval, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ } {fail_only}
+ llvm.return
+}
+
+// Weak compare+capture: {read, compare} with weak
+// CHECK-LABEL: define void @compare_capture_weak(
+// CHECK-SAME: ptr %[[X:.*]], ptr %[[E:.*]], ptr %[[D:.*]], ptr %[[V:.*]])
+// CHECK: %[[EVAL:.*]] = load i32, ptr %[[E]], align 4
+// CHECK: %[[DVAL:.*]] = load i32, ptr %[[D]], align 4
+// CHECK: %[[RESULT:.*]] = cmpxchg weak ptr %[[X]], i32 %[[EVAL]], i32 %[[DVAL]] monotonic monotonic
+// CHECK: %[[OLD:.*]] = extractvalue { i32, i1 } %[[RESULT]], 0
+// CHECK: store i32 %[[OLD]], ptr %[[V]], align 4
+llvm.func @compare_capture_weak(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> i32
+ %dval = llvm.load %d : !llvm.ptr -> i32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: i32):
+ %cmp = llvm.icmp "eq" %xval, %eval : i32
+ %sel = llvm.select %cmp, %dval, %xval : i1, i32
+ omp.yield(%sel : i32)
+ } {weak}
+ }
+ llvm.return
+}
+
+// ===== Float (real) tests =====
+// Float uses the HandleFPNegZero path with NaN/zero checks and bitcast.
+
+// Float prefix compare+capture: v gets old value
+// CHECK-LABEL: define void @compare_capture_float_prefix(
+// CHECK: cmpxchg ptr
+// CHECK: {{.*}}.atomic.exit:
+// CHECK: store float %{{.*}}, ptr %[[V:.*]], align 4
+llvm.func @compare_capture_float_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> f32
+ %dval = llvm.load %d : !llvm.ptr -> f32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: f32):
+ %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+ %sel = llvm.select %cmp, %dval, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ }
+ llvm.return
+}
+
+// Float postfix compare+capture: v gets select(success, d, old)
+// CHECK-LABEL: define void @compare_capture_float_postfix(
+// CHECK: cmpxchg ptr
+// CHECK: {{.*}}.atomic.exit:
+// CHECK: %[[OK:.*]] = phi i1
+// CHECK: %[[OLD_FP:.*]] = bitcast i32 %{{.*}} to float
+// CHECK: %[[NEW:.*]] = select i1 %[[OK]], float %{{.*}}, float %[[OLD_FP]]
+// CHECK: store float %[[NEW]], ptr %{{.*}}, align 4
+llvm.func @compare_capture_float_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> f32
+ %dval = llvm.load %d : !llvm.ptr -> f32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: f32):
+ %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+ %sel = llvm.select %cmp, %dval, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ }
+ llvm.return
+}
+
+// Float fail-only compare+capture: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_float_failonly(
+// CHECK: cmpxchg ptr
+// CHECK: {{.*}}.atomic.exit:
+// CHECK: %[[OK:.*]] = phi i1
+// CHECK: %[[OLD_FP:.*]] = bitcast i32 %{{.*}} to float
+// CHECK: br i1 %[[OK]], label %{{.*}}.atomic.exit{{.*}}, label %{{.*}}.atomic.cont
+// CHECK: {{.*}}.atomic.cont:
+// CHECK: store float %[[OLD_FP]], ptr %{{.*}}
+llvm.func @compare_capture_float_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> f32
+ %dval = llvm.load %d : !llvm.ptr -> f32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: f32):
+ %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+ %sel = llvm.select %cmp, %dval, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ } {fail_only}
+ llvm.return
+}
+
+// ===== Complex (struct<(f32, f32)>) tests =====
+// Complex uses bitcast to i64 for cmpxchg, with FP neg-zero handling.
+
+// Complex prefix compare+capture: v gets old value
+// CHECK-LABEL: define void @compare_capture_complex_prefix(
+// CHECK: cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK: store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_prefix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: !llvm.struct<(f32, f32)>):
+ %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+ %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+ %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+ %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+ %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+ %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+ %cmp = llvm.and %cmpr, %cmpi : i1
+ %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+ omp.yield(%sel : !llvm.struct<(f32, f32)>)
+ }
+ }
+ llvm.return
+}
+
+// Complex postfix compare+capture: v gets select(success, d, old)
+// CHECK-LABEL: define void @compare_capture_complex_postfix(
+// CHECK: cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK: select i1 %{{.*}}, { float, float } %{{.*}}, { float, float } %{{.*}}
+// CHECK: store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_postfix(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: !llvm.struct<(f32, f32)>):
+ %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+ %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+ %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+ %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+ %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+ %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+ %cmp = llvm.and %cmpr, %cmpi : i1
+ %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+ omp.yield(%sel : !llvm.struct<(f32, f32)>)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+ }
+ llvm.return
+}
+
+// Complex fail-only compare+capture: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_complex_failonly(
+// CHECK: cmpxchg ptr %{{.*}}, i64 %{{.*}}, i64 %{{.*}} monotonic monotonic
+// CHECK: br i1 %{{.*}}, label %{{.*}}, label %{{.*}}
+// CHECK: store { float, float } %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_complex_failonly(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ %dval = llvm.load %d : !llvm.ptr -> !llvm.struct<(f32, f32)>
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: !llvm.struct<(f32, f32)>):
+ %xr = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
+ %er = llvm.extractvalue %eval[0] : !llvm.struct<(f32, f32)>
+ %cmpr = llvm.fcmp "oeq" %xr, %er : f32
+ %xi = llvm.extractvalue %xval[1] : !llvm.struct<(f32, f32)>
+ %ei = llvm.extractvalue %eval[1] : !llvm.struct<(f32, f32)>
+ %cmpi = llvm.fcmp "oeq" %xi, %ei : f32
+ %cmp = llvm.and %cmpr, %cmpi : i1
+ %sel = llvm.select %cmp, %dval, %xval : i1, !llvm.struct<(f32, f32)>
+ omp.yield(%sel : !llvm.struct<(f32, f32)>)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, !llvm.struct<(f32, f32)>
+ } {fail_only}
+ llvm.return
+}
+
+// ===== Weak clause with float =====
+
+// Float weak prefix: cmpxchg weak with FP neg-zero handling
+// CHECK-LABEL: define void @compare_capture_float_weak(
+// CHECK: cmpxchg weak ptr %{{.*}}, i32 %{{.*}}, i32 %{{.*}} monotonic monotonic
+// CHECK: store float %{{.*}}, ptr %{{.*}}
+llvm.func @compare_capture_float_weak(%x : !llvm.ptr, %e : !llvm.ptr, %d : !llvm.ptr, %v : !llvm.ptr) {
+ %eval = llvm.load %e : !llvm.ptr -> f32
+ %dval = llvm.load %d : !llvm.ptr -> f32
+ omp.atomic.capture memory_order(relaxed) {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval: f32):
+ %cmp = llvm.fcmp "oeq" %xval, %eval : f32
+ %sel = llvm.select %cmp, %dval, %xval : i1, f32
+ omp.yield(%sel : f32)
+ } {weak}
+ }
+ llvm.return
+}
+
+// ===== Min/Max compare+capture =====
+
+// Integer min, postfix: atomicrmw min, v gets min(old, e)
+// CHECK-LABEL: define void @compare_capture_min_postfix(
+// CHECK-SAME: ptr %[[X:.*]], ptr %[[V:.*]], i32 %[[E:.*]])
+// CHECK: %[[OLD:.*]] = atomicrmw min ptr %[[X]], i32 %[[E]] monotonic
+// CHECK: %[[NEW:.*]] = call i32 @llvm.smin.i32(i32 %[[OLD]], i32 %[[E]])
+// CHECK: store i32 %[[NEW]], ptr %[[V]]
+llvm.func @compare_capture_min_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "sgt" %xval, %e : i32
+ %sel = llvm.select %cmp, %e, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ }
+ llvm.return
+}
+
+// Integer max, postfix: atomicrmw max, v gets max(old, e)
+// CHECK-LABEL: define void @compare_capture_max_postfix(
+// CHECK: %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK: %[[NEW:.*]] = call i32 @llvm.smax.i32(i32 %[[OLD]], i32 %[[E]])
+// CHECK: store i32 %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_max_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "slt" %xval, %e : i32
+ %sel = llvm.select %cmp, %e, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ }
+ llvm.return
+}
+
+// Integer min, prefix: v gets old value
+// CHECK-LABEL: define void @compare_capture_min_prefix(
+// CHECK: %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %{{.*}} monotonic
+// CHECK: store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_min_prefix(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+ omp.atomic.capture {
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "sgt" %xval, %e : i32
+ %sel = llvm.select %cmp, %e, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ }
+ llvm.return
+}
+
+// Float min, postfix: atomicrmw fmin, v gets minnum(old, e)
+// CHECK-LABEL: define void @compare_capture_fmin_postfix(
+// CHECK: %[[OLD:.*]] = atomicrmw fmin ptr %{{.*}}, float %[[E:.*]] monotonic
+// CHECK: %[[NEW:.*]] = call float @llvm.minnum.f32(float %[[OLD]], float %[[E]])
+// CHECK: store float %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_fmin_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : f32):
+ %cmp = llvm.fcmp "ogt" %xval, %e : f32
+ %sel = llvm.select %cmp, %e, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ }
+ llvm.return
+}
+
+// Float max, postfix: atomicrmw fmax, v gets maxnum(old, e)
+// CHECK-LABEL: define void @compare_capture_fmax_postfix(
+// CHECK: %[[OLD:.*]] = atomicrmw fmax ptr %{{.*}}, float %[[E:.*]] monotonic
+// CHECK: %[[NEW:.*]] = call float @llvm.maxnum.f32(float %[[OLD]], float %[[E]])
+// CHECK: store float %[[NEW]], ptr %{{.*}}
+llvm.func @compare_capture_fmax_postfix(%x : !llvm.ptr, %v : !llvm.ptr, %e : f32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : f32):
+ %cmp = llvm.fcmp "olt" %xval, %e : f32
+ %sel = llvm.select %cmp, %e, %xval : i1, f32
+ omp.yield(%sel : f32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, f32
+ }
+ llvm.return
+}
+
+// ===== Min/Max fail-only =====
+
+// Integer min, fail-only: conditional store on failure
+// CHECK-LABEL: define void @compare_capture_min_fail_only(
+// CHECK: %[[OLD:.*]] = atomicrmw min ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK: %[[UPD:.*]] = icmp sgt i32 %[[OLD]], %[[E]]
+// CHECK: %[[FAILED:.*]] = xor i1 %[[UPD]], true
+// CHECK: br i1 %[[FAILED]], label %[[CONT:.*]], label %[[EXIT:.*]]
+// CHECK: [[CONT]]:
+// CHECK: store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_min_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "sgt" %xval, %e : i32
+ %sel = llvm.select %cmp, %e, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ } {fail_only}
+ llvm.return
+}
+
+// Integer max, fail-only
+// CHECK-LABEL: define void @compare_capture_max_fail_only(
+// CHECK: %[[OLD:.*]] = atomicrmw max ptr %{{.*}}, i32 %[[E:.*]] monotonic
+// CHECK: %[[UPD:.*]] = icmp slt i32 %[[OLD]], %[[E]]
+// CHECK: %[[FAILED:.*]] = xor i1 %[[UPD]], true
+// CHECK: br i1 %[[FAILED]], label %[[CONT:.*]], label %[[EXIT:.*]]
+// CHECK: [[CONT]]:
+// CHECK: store i32 %[[OLD]], ptr %{{.*}}
+llvm.func @compare_capture_max_fail_only(%x : !llvm.ptr, %v : !llvm.ptr, %e : i32) {
+ omp.atomic.capture {
+ omp.atomic.compare %x : !llvm.ptr {
+ ^bb0(%xval : i32):
+ %cmp = llvm.icmp "slt" %xval, %e : i32
+ %sel = llvm.select %cmp, %e, %xval : i1, i32
+ omp.yield(%sel : i32)
+ }
+ omp.atomic.read %v = %x : !llvm.ptr, !llvm.ptr, i32
+ } {fail_only}
+ llvm.return
+}
>From ea6c6151e5dce95ca2b26cf7f1d0f7a2cb70c421 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 21 Jul 2026 01:34:26 -0500
Subject: [PATCH 10/11] Using load, fcmp oeq & cmpxchg for complex numbers.
Avoiding CAS loops similar to atomic compare.
---
.../OpenMP/atomic-compare-capture.f90 | 10 +-
.../Integration/OpenMP/atomic-compare.f90 | 41 ++++--
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 124 ++++++++++++------
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 17 ++-
4 files changed, 129 insertions(+), 63 deletions(-)
diff --git a/flang/test/Integration/OpenMP/atomic-compare-capture.f90 b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
index fe97e7b12558b..4f56720b75a0c 100644
--- a/flang/test/Integration/OpenMP/atomic-compare-capture.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare-capture.f90
@@ -102,6 +102,8 @@ subroutine cc_logical(x, e, d, v)
! FIR: fir.cmpc "oeq"
! FIR: }
! LLVM-LABEL: define void @cc_complex_(
+! LLVM: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
! LLVM: cmpxchg ptr %{{.*}}, i64
! LLVM: store { float, float } %{{.*}}, ptr
subroutine cc_complex(x, e, d, v)
@@ -379,6 +381,8 @@ subroutine cc_logical_failonly(x, e, d, v)
! FIR-LABEL: func.func @_QPcc_complex_prefix(
! FIR: omp.atomic.read %{{.*}} : !fir.ref<complex<f32>>, !fir.ref<complex<f32>>, complex<f32>
! LLVM-LABEL: define void @cc_complex_prefix_(
+! LLVM: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
! LLVM: store { float, float } %{{.*}}, ptr
subroutine cc_complex_prefix(x, e, d, v)
@@ -396,8 +400,10 @@ subroutine cc_complex_prefix(x, e, d, v)
! FIR-LABEL: func.func @_QPcc_complex_failonly(
! FIR: } {fail_only}
! LLVM-LABEL: define void @cc_complex_failonly_(
-! LLVM: %[[RES:.*]] = cmpxchg ptr %{{.*}}, i64
-! LLVM: %[[OK:.*]] = extractvalue { i64, i1 } %[[RES]], 1
+! LLVM: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+! LLVM: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+! LLVM: cmpxchg ptr %{{.*}}, i64
+! LLVM: %[[OK:.*]] = phi i1
! LLVM: %[[FAILED:.*]] = xor i1 %[[OK]], true
! LLVM: br i1 %[[FAILED]], label %{{.*}}, label %{{.*}}
! LLVM: store { float, float } %{{.*}}, ptr
diff --git a/flang/test/Integration/OpenMP/atomic-compare.f90 b/flang/test/Integration/OpenMP/atomic-compare.f90
index e98b25e4b4539..88b4bca9e9968 100644
--- a/flang/test/Integration/OpenMP/atomic-compare.f90
+++ b/flang/test/Integration/OpenMP/atomic-compare.f90
@@ -150,32 +150,45 @@ subroutine atomic_compare_real(x, e, d)
if (x == e) x = d
end
-! Complex(4) equality → type-punned i64 cmpxchg with consistent alignment
+! Complex(4) equality -> component-wise IEEE-754 fcmp, then a cmpxchg that swaps
+! using X's loaded bit-pattern as the comparand (so -0.0/+0.0 and NaN follow the
+! scalar float semantics rather than a raw bitwise compare).
!CHECK-LABEL: define void @atomic_compare_complex4_(
!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
-!CHECK: %[[EALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
-!CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN]]
-!CHECK: store { float, float } %{{.*}}, ptr %[[EALLOCA]], align [[ALIGN]]
-!CHECK: %[[EINT:.*]] = load i64, ptr %[[EALLOCA]], align [[ALIGN]]
-!CHECK: store { float, float } %{{.*}}, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: %[[EVAL:.*]] = load { float, float }, ptr %[[E]], align 4
+!CHECK: %[[DVAL:.*]] = load { float, float }, ptr %[[D]], align 4
+!CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
+!CHECK: store { float, float } %[[DVAL]], ptr %[[DALLOCA]], align [[ALIGN]]
!CHECK: %[[DINT:.*]] = load i64, ptr %[[DALLOCA]], align [[ALIGN]]
-!CHECK: cmpxchg ptr %[[X]], i64 %[[EINT]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+!CHECK: %[[XLOAD:.*]] = load atomic i64, ptr %[[X]] monotonic, align [[ALIGN]]
+!CHECK: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+!CHECK: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+!CHECK: %[[EQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+!CHECK: br i1 %[[EQ]], label %[[SWAP:[^,]+]], label %{{.*}}
+!CHECK: [[SWAP]]:
+!CHECK: cmpxchg ptr %[[X]], i64 %[[XLOAD]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
subroutine atomic_compare_complex4(x, e, d)
complex :: x, e, d
!$omp atomic compare
if (x == e) x = d
end
-! Complex(8) equality → type-punned i128 cmpxchg with consistent alignment
+! Complex(8) equality -> component-wise IEEE-754 fcmp, then an i128 cmpxchg using
+! X's loaded bit-pattern as the comparand.
!CHECK-LABEL: define void @atomic_compare_complex8_(
!CHECK-SAME: ptr noalias %[[X:.*]], ptr noalias %[[E:.*]], ptr noalias %[[D:.*]])
-!CHECK: %[[EALLOCA:.*]] = alloca { double, double }, align [[ALIGN:[0-9]+]]
-!CHECK: %[[DALLOCA:.*]] = alloca { double, double }, align [[ALIGN]]
-!CHECK: store { double, double } %{{.*}}, ptr %[[EALLOCA]], align [[ALIGN]]
-!CHECK: %[[EINT:.*]] = load i128, ptr %[[EALLOCA]], align [[ALIGN]]
-!CHECK: store { double, double } %{{.*}}, ptr %[[DALLOCA]], align [[ALIGN]]
+!CHECK: %[[EVAL:.*]] = load { double, double }, ptr %[[E]], align 8
+!CHECK: %[[DVAL:.*]] = load { double, double }, ptr %[[D]], align 8
+!CHECK: %[[DALLOCA:.*]] = alloca { double, double }, align [[ALIGN:[0-9]+]]
+!CHECK: store { double, double } %[[DVAL]], ptr %[[DALLOCA]], align [[ALIGN]]
!CHECK: %[[DINT:.*]] = load i128, ptr %[[DALLOCA]], align [[ALIGN]]
-!CHECK: cmpxchg ptr %[[X]], i128 %[[EINT]], i128 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+!CHECK: %[[XLOAD:.*]] = load atomic i128, ptr %[[X]] monotonic, align [[ALIGN]]
+!CHECK: %[[REEQ:.*]] = fcmp oeq double %[[REX:.*]], %[[REE:.*]]
+!CHECK: %[[IMEQ:.*]] = fcmp oeq double %[[IMX:.*]], %[[IME:.*]]
+!CHECK: %[[EQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+!CHECK: br i1 %[[EQ]], label %[[SWAP:[^,]+]], label %{{.*}}
+!CHECK: [[SWAP]]:
+!CHECK: cmpxchg ptr %[[X]], i128 %[[XLOAD]], i128 %[[DINT]] monotonic monotonic, align [[ALIGN]]
subroutine atomic_compare_complex8(x, e, d)
complex(8) :: x, e, d
!$omp atomic compare
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 060e563ecefa8..fdc8f09fe5a2d 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5422,16 +5422,15 @@ static ComplexComparePattern detectComplexCompareEq(Block &block) {
return result;
}
-/// Emit a bitcast-to-integer `cmpxchg` for a complex (struct-typed) atomic
-/// compare. The expected/desired complex values are spilled to allocas and
-/// reloaded as an integer of the same width, then compared and swapped. Returns
-/// the cmpxchg (result type `{iN, i1}`); `maxAlign` receives the alignment used
-/// so callers can reinterpret the captured old value through memory.
-static llvm::AtomicCmpXchgInst *
-emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
- llvm::Type *complexTy, llvm::Value *eVal,
- llvm::Value *dVal, llvm::AtomicOrdering atomicOrdering,
- bool isWeak, llvm::Align &maxAlign) {
+/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
+/// compare with `fcmp oeq`. The old value of X is returned (as the complex struct
+/// type) in \p oldComplex and the success flag (i1) in \p cmpOk.
+static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
+ llvm::Value *llvmX, llvm::Type *complexTy,
+ llvm::Value *eVal, llvm::Value *dVal,
+ llvm::AtomicOrdering atomicOrdering,
+ bool isWeak, llvm::Value *&oldComplex,
+ llvm::Value *&cmpOk) {
const llvm::DataLayout &DL =
builder.GetInsertBlock()->getModule()->getDataLayout();
unsigned totalBits = DL.getTypeStoreSizeInBits(complexTy).getFixedValue();
@@ -5439,28 +5438,76 @@ emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder, llvm::Value *llvmX,
llvm::IntegerType::get(builder.getContext(), totalBits);
llvm::Align complexAlign = DL.getABITypeAlign(complexTy);
llvm::Align intAlign = DL.getABITypeAlign(intTy);
- maxAlign = std::max(complexAlign, intAlign);
+ llvm::Align maxAlign = std::max(complexAlign, intAlign);
- llvm::AllocaInst *eAlloca =
- builder.CreateAlloca(complexTy, nullptr, "cmplx.e");
- eAlloca->setAlignment(maxAlign);
+ // Spill D to obtain its integer bit pattern for the swap value.
llvm::AllocaInst *dAlloca =
builder.CreateAlloca(complexTy, nullptr, "cmplx.d");
dAlloca->setAlignment(maxAlign);
-
- builder.CreateAlignedStore(eVal, eAlloca, maxAlign);
- llvm::Value *eInt =
- builder.CreateAlignedLoad(intTy, eAlloca, maxAlign, "cmplx.e.int");
builder.CreateAlignedStore(dVal, dAlloca, maxAlign);
llvm::Value *dInt =
builder.CreateAlignedLoad(intTy, dAlloca, maxAlign, "cmplx.d.int");
+ // Load the current value of X atomically and reinterpret it as complex.
+ llvm::LoadInst *xCurr =
+ builder.CreateAlignedLoad(intTy, llvmX, maxAlign, "cmplx.x.load");
+ xCurr->setAtomic(llvm::AtomicOrdering::Monotonic);
+ llvm::AllocaInst *xAlloca =
+ builder.CreateAlloca(complexTy, nullptr, "cmplx.x");
+ xAlloca->setAlignment(maxAlign);
+ builder.CreateAlignedStore(xCurr, xAlloca, maxAlign);
+ llvm::Value *xStruct =
+ builder.CreateAlignedLoad(complexTy, xAlloca, maxAlign, "cmplx.x.val");
+
+ // Component-wise IEEE-754 equality: `fcmp oeq` yields false for NaN (so a
+ // NaN component correctly makes the compare fail) and true for +0.0 vs -0.0
+ // (so a zero-sign difference does not spuriously fail the compare).
+ llvm::Value *reX = builder.CreateExtractValue(xStruct, 0);
+ llvm::Value *imX = builder.CreateExtractValue(xStruct, 1);
+ llvm::Value *reE = builder.CreateExtractValue(eVal, 0);
+ llvm::Value *imE = builder.CreateExtractValue(eVal, 1);
+ llvm::Value *reEq = builder.CreateFCmpOEQ(reX, reE, "cmplx.re.eq");
+ llvm::Value *imEq = builder.CreateFCmpOEQ(imX, imE, "cmplx.im.eq");
+ llvm::Value *fpEqual = builder.CreateAnd(reEq, imEq, "cmplx.eq");
+
+ // When the components compare equal, attempt the swap using X's own loaded
+ // bit pattern as the comparand; otherwise the compare fails and X is left
+ // unchanged (the captured old value is the value just loaded).
+ llvm::BasicBlock *curBB = builder.GetInsertBlock();
+ llvm::Function *fn = curBB->getParent();
+ llvm::BasicBlock *swapBB =
+ llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.swap", fn);
+ llvm::BasicBlock *exitBB =
+ llvm::BasicBlock::Create(builder.getContext(), "cmplx.atomic.exit", fn);
+ builder.CreateCondBr(fpEqual, swapBB, exitBB);
+
+ builder.SetInsertPoint(swapBB);
llvm::AtomicOrdering failOrdering =
llvm::AtomicCmpXchgInst::getStrongestFailureOrdering(atomicOrdering);
- auto *cmpXchg = builder.CreateAtomicCmpXchg(llvmX, eInt, dInt, maxAlign,
- atomicOrdering, failOrdering);
+ llvm::AtomicCmpXchgInst *cmpXchg = builder.CreateAtomicCmpXchg(
+ llvmX, xCurr, dInt, maxAlign, atomicOrdering, failOrdering);
cmpXchg->setWeak(isWeak);
- return cmpXchg;
+ llvm::Value *oldSwap = builder.CreateExtractValue(cmpXchg, 0);
+ llvm::Value *okSwap = builder.CreateExtractValue(cmpXchg, 1);
+ builder.CreateBr(exitBB);
+
+ // Merge the swap and no-swap paths.
+ builder.SetInsertPoint(exitBB);
+ llvm::PHINode *oldIntPHI = builder.CreatePHI(intTy, 2, "cmplx.old.int");
+ oldIntPHI->addIncoming(oldSwap, swapBB);
+ oldIntPHI->addIncoming(xCurr, curBB);
+ llvm::PHINode *okPHI = builder.CreatePHI(builder.getInt1Ty(), 2, "cmplx.ok");
+ okPHI->addIncoming(okSwap, swapBB);
+ okPHI->addIncoming(builder.getFalse(), curBB);
+
+ // Reinterpret the old integer value as the complex struct via memory.
+ llvm::AllocaInst *oldAlloca =
+ builder.CreateAlloca(complexTy, nullptr, "cmplx.old");
+ oldAlloca->setAlignment(maxAlign);
+ builder.CreateAlignedStore(oldIntPHI, oldAlloca, maxAlign);
+ oldComplex = builder.CreateAlignedLoad(complexTy, oldAlloca, maxAlign,
+ "cmplx.old.val");
+ cmpOk = okPHI;
}
/// Holds the extracted comparison pattern information from an atomic compare
@@ -5687,25 +5734,17 @@ convertOmpAtomicCapture(omp::AtomicCaptureOp atomicCaptureOp,
bool isFailOnly = atomicCaptureOp.getFailOnly();
// Complex equality capture: x is struct-typed, which the OMPIRBuilder
- // cannot handle, so emit a bitcast-to-integer cmpxchg (as in the
- // non-capture complex path) and reconstruct the captured value from its
- // result through memory. Complex only supports the == comparison.
+ // cannot handle, so emit an IEEE-754-correct cmpxchg (as in the non-capture
+ // complex path) and reconstruct the captured value from its result. The
+ // helper compares components with `fcmp oeq` so `-0.0 == +0.0` and `NaN`
+ // are handled as in the scalar float path. Complex only supports the ==
+ // comparison.
if (llvmXElementType->isStructTy()) {
- llvm::Align maxAlign;
- llvm::AtomicCmpXchgInst *cmpXchg = emitComplexAtomicCmpXchg(
- builder, llvmX, llvmXElementType, eVal, dVal, atomicOrdering,
- atomicCompareOp.getWeak(), maxAlign);
-
- llvm::Value *oldInt = builder.CreateExtractValue(cmpXchg, 0);
- llvm::Value *cmpOk = builder.CreateExtractValue(cmpXchg, 1);
-
- // Reinterpret the old integer value as the complex struct via memory.
- llvm::AllocaInst *oldAlloca =
- builder.CreateAlloca(llvmXElementType, nullptr, "cmplx.old");
- oldAlloca->setAlignment(maxAlign);
- builder.CreateAlignedStore(oldInt, oldAlloca, maxAlign);
- llvm::Value *oldComplex = builder.CreateAlignedLoad(
- llvmXElementType, oldAlloca, maxAlign, "cmplx.old.val");
+ llvm::Value *oldComplex = nullptr;
+ llvm::Value *cmpOk = nullptr;
+ emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
+ atomicOrdering, atomicCompareOp.getWeak(),
+ oldComplex, cmpOk);
if (isFailOnly) {
// v is written only when the compare fails (cmpOk == false).
@@ -6161,10 +6200,13 @@ convertOmpAtomicCompare(omp::AtomicCompareOp atomicCompareOp,
dVal = materializeValue(yieldOp.getResults()[0]);
}
- llvm::Align maxAlign;
+ llvm::Value *oldComplex = nullptr;
+ llvm::Value *cmpOk = nullptr;
emitComplexAtomicCmpXchg(builder, llvmX, llvmXElementType, eVal, dVal,
atomicOrdering, atomicCompareOp.getWeak(),
- maxAlign);
+ oldComplex, cmpOk);
+ (void)oldComplex;
+ (void)cmpOk;
// Emit flush after atomic compare if needed (for release, acq_rel,
// seq_cst orderings).
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index b779d20f42d0c..63054056adadc 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -2619,14 +2619,19 @@ llvm.func @omp_atomic_compare(
omp.yield(%sel1 : f32)
}
- // Complex equality → bitcasted integer cmpxchg with consistent alignment
- // CHECK: %[[EALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
- // CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN]]
- // CHECK: store { float, float } %[[EC]], ptr %[[EALLOCA]], align [[ALIGN]]
- // CHECK: %[[EINT:.*]] = load i64, ptr %[[EALLOCA]], align [[ALIGN]]
+ // Complex equality → component-wise IEEE-754 fcmp, then a cmpxchg that
+ // swaps using x's loaded bit-pattern as the comparand (so -0.0/+0.0 and NaN
+ // follow the scalar float semantics rather than a raw bitwise compare).
+ // CHECK: %[[DALLOCA:.*]] = alloca { float, float }, align [[ALIGN:[0-9]+]]
// CHECK: store { float, float } %[[DC]], ptr %[[DALLOCA]], align [[ALIGN]]
// CHECK: %[[DINT:.*]] = load i64, ptr %[[DALLOCA]], align [[ALIGN]]
- // CHECK: cmpxchg ptr %[[XC]], i64 %[[EINT]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
+ // CHECK: %[[XLOAD:.*]] = load atomic i64, ptr %[[XC]] monotonic, align [[ALIGN]]
+ // CHECK: %[[REEQ:.*]] = fcmp oeq float %[[REX:.*]], %[[REE:.*]]
+ // CHECK: %[[IMEQ:.*]] = fcmp oeq float %[[IMX:.*]], %[[IME:.*]]
+ // CHECK: %[[CEQ:.*]] = and i1 %[[REEQ]], %[[IMEQ]]
+ // CHECK: br i1 %[[CEQ]], label %[[CSWAP:[^,]+]], label %{{.*}}
+ // CHECK: [[CSWAP]]:
+ // CHECK: cmpxchg ptr %[[XC]], i64 %[[XLOAD]], i64 %[[DINT]] monotonic monotonic, align [[ALIGN]]
omp.atomic.compare %xc : !llvm.ptr {
^bb0(%xval : !llvm.struct<(f32, f32)>):
%re_x = llvm.extractvalue %xval[0] : !llvm.struct<(f32, f32)>
>From a446fe321fd0023edecfbc3734cc761a61ff7fc0 Mon Sep 17 00:00:00 2001
From: Sunil Kuravinakop <kuravina at pe31.hpc.amslabs.hpecorp.net>
Date: Tue, 21 Jul 2026 02:05:43 -0500
Subject: [PATCH 11/11] Correcting a code formatting error.
---
.../LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index fdc8f09fe5a2d..69b722582d785 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -5423,8 +5423,8 @@ static ComplexComparePattern detectComplexCompareEq(Block &block) {
}
/// Emit an IEEE-754-correct `cmpxchg` for a complex (struct-typed) atomic
-/// compare with `fcmp oeq`. The old value of X is returned (as the complex struct
-/// type) in \p oldComplex and the success flag (i1) in \p cmpOk.
+/// compare with `fcmp oeq`. The old value of X is returned (as the complex
+/// struct type) in \p oldComplex and the success flag (i1) in \p cmpOk.
static void emitComplexAtomicCmpXchg(llvm::IRBuilderBase &builder,
llvm::Value *llvmX, llvm::Type *complexTy,
llvm::Value *eVal, llvm::Value *dVal,
More information about the flang-commits
mailing list