Skip to content

Commit 990fe80

Browse files
authored
[CIR] Add support for atomic compare-and-swap (#156253)
This patch adds support for atomic compare-and-swap operations, including the following C/C++ instrinsics: - `__atomic_compare_exchange` - `__atomic_compare_exchange_n` - `__c11_atomic_compare_exchange_strong` - `__c11_atomic_compare_exchange_weak`
1 parent 54b3dc1 commit 990fe80

File tree

6 files changed

+491
-27
lines changed

6 files changed

+491
-27
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4000,4 +4000,77 @@ def CIR_ThrowOp : CIR_Op<"throw"> {
40004000
let hasVerifier = 1;
40014001
}
40024002

4003+
//===----------------------------------------------------------------------===//
4004+
// Atomic operations
4005+
//===----------------------------------------------------------------------===//
4006+
4007+
def CIR_AtomicCmpXchg : CIR_Op<"atomic.cmpxchg", [
4008+
AllTypesMatch<["old", "expected", "desired"]>
4009+
]> {
4010+
let summary = "Atomic compare and exchange";
4011+
let description = [{
4012+
C/C++ atomic compare and exchange operation. Implements builtins like
4013+
`__atomic_compare_exchange_n` and `__atomic_compare_exchange`.
4014+
4015+
This operation takes three arguments: a pointer `ptr` and two values
4016+
`expected` and `desired`. This operation compares the value of the object
4017+
pointed-to by `ptr` with `expected`, and if they are equal, it sets the
4018+
value of the object to `desired`.
4019+
4020+
The `succ_order` attribute gives the memory order of this atomic operation
4021+
when the exchange takes place. The `fail_order` attribute gives the memory
4022+
order of this atomic operation when the exchange does not take place.
4023+
4024+
The `weak` attribute is a boolean flag that indicates whether this is a
4025+
"weak" compare-and-exchange operation. A weak compare-and-exchange operation
4026+
allows "spurious failures", meaning that be treated as if the comparison
4027+
failed and not exchange values even if `*ptr` and `expected` indeed compare
4028+
equal.
4029+
4030+
The type of `expected` and `desired` must be the same. The pointee type of
4031+
`ptr` must be the same as the type of `expected` and `desired`.
4032+
4033+
This operation has two results. The first result `old` gives the old value
4034+
of the object pointed-to by `ptr`, regardless of whether the exchange
4035+
actually took place. The second result `success` is a boolean flag
4036+
indicating whether the exchange actually took place.
4037+
4038+
Example:
4039+
4040+
```mlir
4041+
%old, %success = cir.atomic.cmpxchg(%ptr : !cir.ptr<!u64i>,
4042+
%expected : !u64i,
4043+
%desired : !u64i,
4044+
success = seq_cst,
4045+
failure = seq_cst) weak
4046+
: (!u64i, !cir.bool)
4047+
```
4048+
}];
4049+
let results = (outs CIR_AnyType:$old, CIR_BoolType:$success);
4050+
let arguments = (ins Arg<CIR_PointerType, "", [MemRead, MemWrite]>:$ptr,
4051+
CIR_AnyType:$expected,
4052+
CIR_AnyType:$desired,
4053+
Arg<CIR_MemOrder, "success memory order">:$succ_order,
4054+
Arg<CIR_MemOrder, "failure memory order">:$fail_order,
4055+
OptionalAttr<I64Attr>:$alignment,
4056+
UnitAttr:$weak,
4057+
UnitAttr:$is_volatile);
4058+
4059+
let assemblyFormat = [{
4060+
`(`
4061+
$ptr `:` qualified(type($ptr)) `,`
4062+
$expected `:` type($expected) `,`
4063+
$desired `:` type($desired) `,`
4064+
`success` `=` $succ_order `,`
4065+
`failure` `=` $fail_order
4066+
`)`
4067+
(`align` `(` $alignment^ `)`)?
4068+
(`weak` $weak^)?
4069+
(`volatile` $is_volatile^)?
4070+
`:` `(` type($old) `,` type($success) `)` attr-dict
4071+
}];
4072+
4073+
let hasVerifier = 1;
4074+
}
4075+
40034076
#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD

clang/lib/CIR/CodeGen/CIRGenAtomic.cpp

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,94 @@ void AtomicInfo::emitCopyIntoMemory(RValue rvalue) const {
244244
}
245245
}
246246

247+
static void emitAtomicCmpXchg(CIRGenFunction &cgf, AtomicExpr *e, bool isWeak,
248+
Address dest, Address ptr, Address val1,
249+
Address val2, uint64_t size,
250+
cir::MemOrder successOrder,
251+
cir::MemOrder failureOrder) {
252+
mlir::Location loc = cgf.getLoc(e->getSourceRange());
253+
254+
CIRGenBuilderTy &builder = cgf.getBuilder();
255+
mlir::Value expected = builder.createLoad(loc, val1);
256+
mlir::Value desired = builder.createLoad(loc, val2);
257+
258+
auto cmpxchg = cir::AtomicCmpXchg::create(
259+
builder, loc, expected.getType(), builder.getBoolTy(), ptr.getPointer(),
260+
expected, desired,
261+
cir::MemOrderAttr::get(&cgf.getMLIRContext(), successOrder),
262+
cir::MemOrderAttr::get(&cgf.getMLIRContext(), failureOrder),
263+
builder.getI64IntegerAttr(ptr.getAlignment().getAsAlign().value()));
264+
265+
cmpxchg.setIsVolatile(e->isVolatile());
266+
cmpxchg.setWeak(isWeak);
267+
268+
mlir::Value failed = builder.createNot(cmpxchg.getSuccess());
269+
cir::IfOp::create(builder, loc, failed, /*withElseRegion=*/false,
270+
[&](mlir::OpBuilder &, mlir::Location) {
271+
auto ptrTy = mlir::cast<cir::PointerType>(
272+
val1.getPointer().getType());
273+
if (val1.getElementType() != ptrTy.getPointee()) {
274+
val1 = val1.withPointer(builder.createPtrBitcast(
275+
val1.getPointer(), val1.getElementType()));
276+
}
277+
builder.createStore(loc, cmpxchg.getOld(), val1);
278+
builder.createYield(loc);
279+
});
280+
281+
// Update the memory at Dest with Success's value.
282+
cgf.emitStoreOfScalar(cmpxchg.getSuccess(),
283+
cgf.makeAddrLValue(dest, e->getType()),
284+
/*isInit=*/false);
285+
}
286+
287+
static void emitAtomicCmpXchgFailureSet(CIRGenFunction &cgf, AtomicExpr *e,
288+
bool isWeak, Address dest, Address ptr,
289+
Address val1, Address val2,
290+
Expr *failureOrderExpr, uint64_t size,
291+
cir::MemOrder successOrder) {
292+
Expr::EvalResult failureOrderEval;
293+
if (failureOrderExpr->EvaluateAsInt(failureOrderEval, cgf.getContext())) {
294+
uint64_t failureOrderInt = failureOrderEval.Val.getInt().getZExtValue();
295+
296+
cir::MemOrder failureOrder;
297+
if (!cir::isValidCIRAtomicOrderingCABI(failureOrderInt)) {
298+
failureOrder = cir::MemOrder::Relaxed;
299+
} else {
300+
switch ((cir::MemOrder)failureOrderInt) {
301+
case cir::MemOrder::Relaxed:
302+
// 31.7.2.18: "The failure argument shall not be memory_order_release
303+
// nor memory_order_acq_rel". Fallback to monotonic.
304+
case cir::MemOrder::Release:
305+
case cir::MemOrder::AcquireRelease:
306+
failureOrder = cir::MemOrder::Relaxed;
307+
break;
308+
case cir::MemOrder::Consume:
309+
case cir::MemOrder::Acquire:
310+
failureOrder = cir::MemOrder::Acquire;
311+
break;
312+
case cir::MemOrder::SequentiallyConsistent:
313+
failureOrder = cir::MemOrder::SequentiallyConsistent;
314+
break;
315+
}
316+
}
317+
318+
// Prior to c++17, "the failure argument shall be no stronger than the
319+
// success argument". This condition has been lifted and the only
320+
// precondition is 31.7.2.18. Effectively treat this as a DR and skip
321+
// language version checks.
322+
emitAtomicCmpXchg(cgf, e, isWeak, dest, ptr, val1, val2, size, successOrder,
323+
failureOrder);
324+
return;
325+
}
326+
327+
assert(!cir::MissingFeatures::atomicExpr());
328+
cgf.cgm.errorNYI(e->getSourceRange(),
329+
"emitAtomicCmpXchgFailureSet: non-constant failure order");
330+
}
331+
247332
static void emitAtomicOp(CIRGenFunction &cgf, AtomicExpr *expr, Address dest,
248-
Address ptr, Address val1, uint64_t size,
333+
Address ptr, Address val1, Address val2,
334+
Expr *isWeakExpr, Expr *failureOrderExpr, int64_t size,
249335
cir::MemOrder order) {
250336
std::unique_ptr<AtomicScopeModel> scopeModel = expr->getScopeModel();
251337
if (scopeModel) {
@@ -264,6 +350,30 @@ static void emitAtomicOp(CIRGenFunction &cgf, AtomicExpr *expr, Address dest,
264350
case AtomicExpr::AO__c11_atomic_init:
265351
llvm_unreachable("already handled!");
266352

353+
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
354+
emitAtomicCmpXchgFailureSet(cgf, expr, /*isWeak=*/false, dest, ptr, val1,
355+
val2, failureOrderExpr, size, order);
356+
return;
357+
358+
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
359+
emitAtomicCmpXchgFailureSet(cgf, expr, /*isWeak=*/true, dest, ptr, val1,
360+
val2, failureOrderExpr, size, order);
361+
return;
362+
363+
case AtomicExpr::AO__atomic_compare_exchange:
364+
case AtomicExpr::AO__atomic_compare_exchange_n: {
365+
bool isWeak = false;
366+
if (isWeakExpr->EvaluateAsBooleanCondition(isWeak, cgf.getContext())) {
367+
emitAtomicCmpXchgFailureSet(cgf, expr, isWeak, dest, ptr, val1, val2,
368+
failureOrderExpr, size, order);
369+
} else {
370+
assert(!cir::MissingFeatures::atomicExpr());
371+
cgf.cgm.errorNYI(expr->getSourceRange(),
372+
"emitAtomicOp: non-constant isWeak");
373+
}
374+
return;
375+
}
376+
267377
case AtomicExpr::AO__c11_atomic_load:
268378
case AtomicExpr::AO__atomic_load_n:
269379
case AtomicExpr::AO__atomic_load: {
@@ -292,16 +402,12 @@ static void emitAtomicOp(CIRGenFunction &cgf, AtomicExpr *expr, Address dest,
292402

293403
case AtomicExpr::AO__opencl_atomic_init:
294404

295-
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
296405
case AtomicExpr::AO__hip_atomic_compare_exchange_strong:
297406
case AtomicExpr::AO__opencl_atomic_compare_exchange_strong:
298407

299-
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
300408
case AtomicExpr::AO__opencl_atomic_compare_exchange_weak:
301409
case AtomicExpr::AO__hip_atomic_compare_exchange_weak:
302410

303-
case AtomicExpr::AO__atomic_compare_exchange:
304-
case AtomicExpr::AO__atomic_compare_exchange_n:
305411
case AtomicExpr::AO__scoped_atomic_compare_exchange:
306412
case AtomicExpr::AO__scoped_atomic_compare_exchange_n:
307413

@@ -421,7 +527,11 @@ RValue CIRGenFunction::emitAtomicExpr(AtomicExpr *e) {
421527
if (const auto *ty = atomicTy->getAs<AtomicType>())
422528
memTy = ty->getValueType();
423529

530+
Expr *isWeakExpr = nullptr;
531+
Expr *orderFailExpr = nullptr;
532+
424533
Address val1 = Address::invalid();
534+
Address val2 = Address::invalid();
425535
Address dest = Address::invalid();
426536
Address ptr = emitPointerWithAlignment(e->getPtr());
427537

@@ -462,6 +572,24 @@ RValue CIRGenFunction::emitAtomicExpr(AtomicExpr *e) {
462572
val1 = emitPointerWithAlignment(e->getVal1());
463573
break;
464574

575+
case AtomicExpr::AO__atomic_compare_exchange:
576+
case AtomicExpr::AO__atomic_compare_exchange_n:
577+
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
578+
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
579+
val1 = emitPointerWithAlignment(e->getVal1());
580+
if (e->getOp() == AtomicExpr::AO__atomic_compare_exchange ||
581+
e->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange)
582+
val2 = emitPointerWithAlignment(e->getVal2());
583+
else
584+
val2 = emitValToTemp(*this, e->getVal2());
585+
orderFailExpr = e->getOrderFail();
586+
if (e->getOp() == AtomicExpr::AO__atomic_compare_exchange_n ||
587+
e->getOp() == AtomicExpr::AO__atomic_compare_exchange ||
588+
e->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange_n ||
589+
e->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange)
590+
isWeakExpr = e->getWeak();
591+
break;
592+
465593
case AtomicExpr::AO__atomic_store_n:
466594
case AtomicExpr::AO__c11_atomic_store:
467595
val1 = emitValToTemp(*this, e->getVal1());
@@ -484,6 +612,8 @@ RValue CIRGenFunction::emitAtomicExpr(AtomicExpr *e) {
484612
if (dest.isValid()) {
485613
if (shouldCastToIntPtrTy)
486614
dest = atomics.castToAtomicIntPointer(dest);
615+
} else if (e->isCmpXChg()) {
616+
dest = createMemTemp(resultTy, getLoc(e->getSourceRange()), "cmpxchg.bool");
487617
} else if (!resultTy->isVoidType()) {
488618
dest = atomics.createTempAlloca();
489619
if (shouldCastToIntPtrTy)
@@ -530,8 +660,8 @@ RValue CIRGenFunction::emitAtomicExpr(AtomicExpr *e) {
530660
// value, but it's hard to enforce that in general.
531661
uint64_t ord = orderConst.Val.getInt().getZExtValue();
532662
if (isMemOrderValid(ord, isStore, isLoad))
533-
emitAtomicOp(*this, e, dest, ptr, val1, size,
534-
static_cast<cir::MemOrder>(ord));
663+
emitAtomicOp(*this, e, dest, ptr, val1, val2, isWeakExpr, orderFailExpr,
664+
size, static_cast<cir::MemOrder>(ord));
535665
} else {
536666
assert(!cir::MissingFeatures::atomicExpr());
537667
cgm.errorNYI(e->getSourceRange(), "emitAtomicExpr: dynamic memory order");

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,6 +2730,20 @@ mlir::LogicalResult cir::ThrowOp::verify() {
27302730
return failure();
27312731
}
27322732

2733+
//===----------------------------------------------------------------------===//
2734+
// AtomicCmpXchg
2735+
//===----------------------------------------------------------------------===//
2736+
2737+
LogicalResult cir::AtomicCmpXchg::verify() {
2738+
mlir::Type pointeeType = getPtr().getType().getPointee();
2739+
2740+
if (pointeeType != getExpected().getType() ||
2741+
pointeeType != getDesired().getType())
2742+
return emitOpError("ptr, expected and desired types must match");
2743+
2744+
return success();
2745+
}
2746+
27332747
//===----------------------------------------------------------------------===//
27342748
// TableGen'd op method definitions
27352749
//===----------------------------------------------------------------------===//

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,51 @@ mlir::LogicalResult CIRToLLVMAssumeSepStorageOpLowering::matchAndRewrite(
640640
return mlir::success();
641641
}
642642

643+
static mlir::LLVM::AtomicOrdering
644+
getLLVMMemOrder(std::optional<cir::MemOrder> memorder) {
645+
if (!memorder)
646+
return mlir::LLVM::AtomicOrdering::not_atomic;
647+
switch (*memorder) {
648+
case cir::MemOrder::Relaxed:
649+
return mlir::LLVM::AtomicOrdering::monotonic;
650+
case cir::MemOrder::Consume:
651+
case cir::MemOrder::Acquire:
652+
return mlir::LLVM::AtomicOrdering::acquire;
653+
case cir::MemOrder::Release:
654+
return mlir::LLVM::AtomicOrdering::release;
655+
case cir::MemOrder::AcquireRelease:
656+
return mlir::LLVM::AtomicOrdering::acq_rel;
657+
case cir::MemOrder::SequentiallyConsistent:
658+
return mlir::LLVM::AtomicOrdering::seq_cst;
659+
}
660+
llvm_unreachable("unknown memory order");
661+
}
662+
663+
mlir::LogicalResult CIRToLLVMAtomicCmpXchgLowering::matchAndRewrite(
664+
cir::AtomicCmpXchg op, OpAdaptor adaptor,
665+
mlir::ConversionPatternRewriter &rewriter) const {
666+
mlir::Value expected = adaptor.getExpected();
667+
mlir::Value desired = adaptor.getDesired();
668+
669+
auto cmpxchg = mlir::LLVM::AtomicCmpXchgOp::create(
670+
rewriter, op.getLoc(), adaptor.getPtr(), expected, desired,
671+
getLLVMMemOrder(adaptor.getSuccOrder()),
672+
getLLVMMemOrder(adaptor.getFailOrder()));
673+
assert(!cir::MissingFeatures::atomicScope());
674+
cmpxchg.setAlignment(adaptor.getAlignment());
675+
cmpxchg.setWeak(adaptor.getWeak());
676+
cmpxchg.setVolatile_(adaptor.getIsVolatile());
677+
678+
// Check result and apply stores accordingly.
679+
auto old = mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(),
680+
cmpxchg.getResult(), 0);
681+
auto cmp = mlir::LLVM::ExtractValueOp::create(rewriter, op.getLoc(),
682+
cmpxchg.getResult(), 1);
683+
684+
rewriter.replaceOp(op, {old, cmp});
685+
return mlir::success();
686+
}
687+
643688
mlir::LogicalResult CIRToLLVMBitClrsbOpLowering::matchAndRewrite(
644689
cir::BitClrsbOp op, OpAdaptor adaptor,
645690
mlir::ConversionPatternRewriter &rewriter) const {
@@ -1202,26 +1247,6 @@ mlir::LogicalResult CIRToLLVMFrameAddrOpLowering::matchAndRewrite(
12021247
return mlir::success();
12031248
}
12041249

1205-
static mlir::LLVM::AtomicOrdering
1206-
getLLVMMemOrder(std::optional<cir::MemOrder> memorder) {
1207-
if (!memorder)
1208-
return mlir::LLVM::AtomicOrdering::not_atomic;
1209-
switch (*memorder) {
1210-
case cir::MemOrder::Relaxed:
1211-
return mlir::LLVM::AtomicOrdering::monotonic;
1212-
case cir::MemOrder::Consume:
1213-
case cir::MemOrder::Acquire:
1214-
return mlir::LLVM::AtomicOrdering::acquire;
1215-
case cir::MemOrder::Release:
1216-
return mlir::LLVM::AtomicOrdering::release;
1217-
case cir::MemOrder::AcquireRelease:
1218-
return mlir::LLVM::AtomicOrdering::acq_rel;
1219-
case cir::MemOrder::SequentiallyConsistent:
1220-
return mlir::LLVM::AtomicOrdering::seq_cst;
1221-
}
1222-
llvm_unreachable("unknown memory order");
1223-
}
1224-
12251250
mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
12261251
cir::LoadOp op, OpAdaptor adaptor,
12271252
mlir::ConversionPatternRewriter &rewriter) const {
@@ -2429,6 +2454,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
24292454
CIRToLLVMAssumeOpLowering,
24302455
CIRToLLVMAssumeAlignedOpLowering,
24312456
CIRToLLVMAssumeSepStorageOpLowering,
2457+
CIRToLLVMAtomicCmpXchgLowering,
24322458
CIRToLLVMBaseClassAddrOpLowering,
24332459
CIRToLLVMBinOpLowering,
24342460
CIRToLLVMBitClrsbOpLowering,

0 commit comments

Comments
 (0)