[clang] [CIR] CIRGen via libcall for OpenCL atomic builtins (PR #229423)
Sirui Mu via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 11 07:21:31 PDT 2026
https://github.com/Lancern updated https://github.com/llvm/llvm-project/pull/229423
>From 8096cd56ac7c828de75a146c5b22aecca2958796 Mon Sep 17 00:00:00 2001
From: Sirui Mu <msrlancern at gmail.com>
Date: Tue, 6 Oct 2026 21:34:12 +0800
Subject: [PATCH] [CIR] CIRGen via libcall for OpenCL atomic builtins
This patch adds CIRGen support for OpenCL atomic builtins that would go through
libcalls.
Assisted-by: codex / gpt-6.1 sol light
---
clang/lib/CIR/CodeGen/CIRGenAtomic.cpp | 46 ++++++------
.../test/CIR/CodeGen/atomic-libcall-opencl.c | 70 +++++++++++++++++++
2 files changed, 95 insertions(+), 21 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/atomic-libcall-opencl.c
diff --git a/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp b/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
index d974f0792d481..a0bf0dab88627 100644
--- a/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
@@ -1183,8 +1183,8 @@ static RValue emitAtomicLibCall(CIRGenFunction &cgf, llvm::StringRef funcName,
static RValue emitLibCallForAtomicExpr(CIRGenFunction &cgf, AtomicExpr *e,
Address atomicPtr, Address dest,
Address val1, Address val2,
- uint64_t atomicTySize,
- QualType resultTy) {
+ uint64_t atomicTySize, QualType resultTy,
+ mlir::Value scope) {
mlir::Location loc = cgf.getLoc(e->getSourceRange());
CallArgList args;
@@ -1197,12 +1197,20 @@ static RValue emitLibCallForAtomicExpr(CIRGenFunction &cgf, AtomicExpr *e,
// The OpenCL atomic library functions only accept pointer arguments to
// generic address space.
auto castToGenericAddrSpace = [&](mlir::Value v, QualType pt) {
- if (!e->isOpenCL())
- return cgf.getBuilder().createPtrBitcast(v, cgf.voidTy);
-
- assert(!cir::MissingFeatures::openCL());
- cgf.cgm.errorNYI(loc, "emitLibCallForAtomicExpr: openCL");
- return cgf.getBuilder().createPtrBitcast(v, cgf.voidTy);
+ v = cgf.getBuilder().createPtrBitcast(v, cgf.voidTy);
+ if (!e->isOpenCL() || !pt->isPointerType())
+ return v;
+
+ LangAS addrSpace =
+ pt->castAs<clang::PointerType>()->getPointeeType().getAddressSpace();
+ if (addrSpace == LangAS::opencl_generic)
+ return v;
+
+ auto destAddrSpace = cir::TargetAddressSpaceAttr::get(
+ &cgf.getMLIRContext(),
+ cgf.getContext().getTargetAddressSpace(LangAS::opencl_generic));
+ mlir::Type destType = cir::PointerType::get(cgf.voidTy, destAddrSpace);
+ return cgf.performAddrSpaceCast(v, destType);
};
args.add(RValue::get(castToGenericAddrSpace(atomicPtr.emitRawPointer(),
e->getPtr()->getType())),
@@ -1211,7 +1219,7 @@ static RValue emitLibCallForAtomicExpr(CIRGenFunction &cgf, AtomicExpr *e,
mlir::Value order = cgf.emitScalarExpr(e->getOrder());
// The next 1-3 parameters are op-dependent.
- llvm::StringRef calleeName;
+ std::string calleeName;
QualType retTy;
bool hasRetTy = false;
switch (e->getOp()) {
@@ -1362,27 +1370,23 @@ static RValue emitLibCallForAtomicExpr(CIRGenFunction &cgf, AtomicExpr *e,
llvm_unreachable("Integral atomic operations always become atomicrmw!");
}
- if (e->isOpenCL()) {
- assert(!cir::MissingFeatures::openCL());
- cgf.cgm.errorNYI(loc, "emitLibCallForAtomicExpr: openCL");
- return RValue::get(nullptr);
- }
+ if (e->isOpenCL())
+ calleeName =
+ std::string("__opencl") + StringRef(calleeName).drop_front(1).str();
// By default, assume we return a value of the atomic type.
if (!hasRetTy) {
// Value is returned through parameter before the order.
retTy = cgf.getContext().VoidTy;
- args.add(RValue::get(castToGenericAddrSpace(dest.emitRawPointer(), retTy)),
+ args.add(RValue::get(castToGenericAddrSpace(dest.emitRawPointer(),
+ cgf.getContext().VoidPtrTy)),
cgf.getContext().VoidPtrTy);
}
// Order is always the last parameter.
args.add(RValue::get(order), cgf.getContext().IntTy);
- if (e->isOpenCL()) {
- assert(!cir::MissingFeatures::openCL());
- cgf.cgm.errorNYI(loc, "emitLibCallForAtomicExpr: openCL");
- return RValue::get(nullptr);
- }
+ if (e->isOpenCL())
+ args.add(RValue::get(scope), cgf.getContext().IntTy);
RValue res = emitAtomicLibCall(cgf, calleeName, retTy, args);
@@ -1646,7 +1650,7 @@ RValue CIRGenFunction::emitAtomicExpr(AtomicExpr *e) {
// See: https://llvm.org/docs/Atomics.html#libcalls-atomic
if (useLibCall)
return emitLibCallForAtomicExpr(*this, e, ptr, dest, val1, val2, size,
- resultTy);
+ resultTy, scope);
bool isStore = e->getOp() == AtomicExpr::AO__c11_atomic_store ||
e->getOp() == AtomicExpr::AO__opencl_atomic_store ||
diff --git a/clang/test/CIR/CodeGen/atomic-libcall-opencl.c b/clang/test/CIR/CodeGen/atomic-libcall-opencl.c
new file mode 100644
index 0000000000000..71791f92ab75c
--- /dev/null
+++ b/clang/test/CIR/CodeGen/atomic-libcall-opencl.c
@@ -0,0 +1,70 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-atomic-alignment -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-atomic-alignment -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
+
+// A 24-byte atomic forces frontend libcalls. OpenCL libcalls use the
+// __opencl_atomic_ prefix and take the memory scope after the memory orders.
+struct Big {
+ int x[6];
+};
+
+// CIR-LABEL: @load(
+// CIR-DAG: %[[ORDER:.+]] = cir.const #cir.int<5> : !s32i
+// CIR-DAG: %[[SCOPE:.+]] = cir.const #cir.int<2> : !s32i
+// CIR: cir.call @__opencl_atomic_load(%{{.+}}, %{{.+}}, %{{.+}}, %[[ORDER]], %[[SCOPE]])
+// LLVM-LABEL: @load(
+// LLVM: call void @__opencl_atomic_load(i64 noundef 24, ptr noundef %{{.+}}, ptr noundef %{{.+}}, i32 noundef 5, i32 noundef 2)
+struct Big load(_Atomic(struct Big) *p) {
+ return __opencl_atomic_load(p, __ATOMIC_SEQ_CST, __OPENCL_MEMORY_SCOPE_DEVICE);
+}
+
+// CIR-LABEL: @store(
+// CIR-DAG: %[[ORDER:.+]] = cir.const #cir.int<3> : !s32i
+// CIR-DAG: %[[SCOPE:.+]] = cir.const #cir.int<1> : !s32i
+// CIR: cir.call @__opencl_atomic_store(%{{.+}}, %{{.+}}, %{{.+}}, %[[ORDER]], %[[SCOPE]])
+// LLVM-LABEL: @store(
+// LLVM: call void @__opencl_atomic_store(i64 noundef 24, ptr noundef %{{.+}}, ptr noundef %{{.+}}, i32 noundef 3, i32 noundef 1)
+void store(_Atomic(struct Big) *p, struct Big v) {
+ __opencl_atomic_store(p, v, __ATOMIC_RELEASE, __OPENCL_MEMORY_SCOPE_WORK_GROUP);
+}
+
+// CIR-LABEL: @exchange(
+// CIR-DAG: %[[SCOPE:.+]] = cir.load {{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR-DAG: %[[ORDER:.+]] = cir.const #cir.int<4> : !s32i
+// CIR: cir.call @__opencl_atomic_exchange(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %[[ORDER]], %[[SCOPE]])
+// LLVM-LABEL: @exchange(
+// LLVM: call void @__opencl_atomic_exchange(i64 noundef 24, ptr noundef %{{.+}}, ptr noundef %{{.+}}, ptr noundef %{{.+}}, i32 noundef 4, i32 noundef %{{.+}})
+struct Big exchange(_Atomic(struct Big) *p, struct Big v, int scope) {
+ return __opencl_atomic_exchange(p, v, __ATOMIC_ACQ_REL, scope);
+}
+
+// CIR-LABEL: @cmpxchg_strong(
+// CIR-DAG: %[[SUCCESS:.+]] = cir.const #cir.int<4> : !s32i
+// CIR-DAG: %[[FAILURE:.+]] = cir.const #cir.int<2> : !s32i
+// CIR-DAG: %[[SCOPE:.+]] = cir.const #cir.int<3> : !s32i
+// CIR: cir.call @__opencl_atomic_compare_exchange(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %[[SUCCESS]], %[[FAILURE]], %[[SCOPE]])
+// LLVM-LABEL: @cmpxchg_strong(
+// LLVM: call zeroext i1 @__opencl_atomic_compare_exchange(i64 noundef 24, ptr noundef %{{.+}}, ptr noundef %{{.+}}, ptr noundef %{{.+}}, i32 noundef 4, i32 noundef 2, i32 noundef 3)
+_Bool cmpxchg_strong(_Atomic(struct Big) *p, struct Big *expected,
+ struct Big desired) {
+ return __opencl_atomic_compare_exchange_strong(
+ p, expected, desired, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE,
+ __OPENCL_MEMORY_SCOPE_ALL_SVM_DEVICES);
+}
+
+// CIR-LABEL: @cmpxchg_weak(
+// CIR-DAG: %[[SUCCESS_SLOT:.+]] = cir.alloca "success" {{.*}} : !cir.ptr<!s32i>
+// CIR-DAG: %[[FAILURE_SLOT:.+]] = cir.alloca "failure" {{.*}} : !cir.ptr<!s32i>
+// CIR-DAG: %[[SCOPE_SLOT:.+]] = cir.alloca "scope" {{.*}} : !cir.ptr<!s32i>
+// CIR-DAG: %[[SUCCESS:.+]] = cir.load {{.*}} %[[SUCCESS_SLOT]] : !cir.ptr<!s32i>, !s32i
+// CIR-DAG: %[[FAILURE:.+]] = cir.load {{.*}} %[[FAILURE_SLOT]] : !cir.ptr<!s32i>, !s32i
+// CIR-DAG: %[[SCOPE:.+]] = cir.load {{.*}} %[[SCOPE_SLOT]] : !cir.ptr<!s32i>, !s32i
+// CIR: cir.call @__opencl_atomic_compare_exchange(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %[[SUCCESS]], %[[FAILURE]], %[[SCOPE]])
+// LLVM-LABEL: @cmpxchg_weak(
+// LLVM: call zeroext i1 @__opencl_atomic_compare_exchange(i64 noundef 24, ptr noundef %{{.+}}, ptr noundef %{{.+}}, ptr noundef %{{.+}}, i32 noundef %{{.+}}, i32 noundef %{{.+}}, i32 noundef %{{.+}})
+_Bool cmpxchg_weak(_Atomic(struct Big) *p, struct Big *expected,
+ struct Big desired, int success, int failure, int scope) {
+ return __opencl_atomic_compare_exchange_weak(p, expected, desired, success,
+ failure, scope);
+}
More information about the cfe-commits
mailing list