Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/AST/StmtOpenACC.h
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ class OpenACCAtomicConstruct final
struct StmtInfo {
const Expr *V;
const Expr *X;
const Expr *Expr;
Copy link
Contributor

@ro-i ro-i Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this leads to build errors due to clash with declaration of clang::Expr in clang/include/clang/AST/Expr.h
Edit: too late, thanks ^^

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically build warnings :) But yes, I have a fix incoming.

// TODO: OpenACC: We should expand this as we're implementing the other
// atomic construct kinds.
};
Expand Down
44 changes: 32 additions & 12 deletions clang/lib/AST/StmtOpenACC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ OpenACCAtomicConstruct *OpenACCAtomicConstruct::Create(
return Inst;
}

static std::pair<const Expr *, const Expr *> getBinaryOpArgs(const Expr *Op) {
if (const auto *BO = dyn_cast<BinaryOperator>(Op)) {
assert(BO->getOpcode() == BO_Assign);
return {BO->getLHS(), BO->getRHS()};
}

const auto *OO = cast<CXXOperatorCallExpr>(Op);
assert(OO->getOperator() == OO_Equal);

return {OO->getArg(0), OO->getArg(1)};
}

const OpenACCAtomicConstruct::StmtInfo
OpenACCAtomicConstruct::getAssociatedStmtInfo() const {
// This ends up being a vastly simplified version of SemaOpenACCAtomic, since
Expand All @@ -333,27 +345,35 @@ OpenACCAtomicConstruct::getAssociatedStmtInfo() const {

switch (AtomicKind) {
case OpenACCAtomicKind::None:
case OpenACCAtomicKind::Write:
case OpenACCAtomicKind::Update:
case OpenACCAtomicKind::Capture:
assert(false && "Only 'read' has been implemented here");
assert(false && "Only 'read'/'write' has been implemented here");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert(false && "Only 'read'/'write' has been implemented here");
assert(false && "Only 'read'/'write' have been implemented here");

return {};
case OpenACCAtomicKind::Read: {
// Read only supports the format 'v = x'; where both sides are a scalar
// expression. This can come in 2 forms; BinaryOperator or
// CXXOperatorCallExpr (rarely).
const Expr *AssignExpr = cast<const Expr>(getAssociatedStmt());
if (const auto *BO = dyn_cast<BinaryOperator>(AssignExpr)) {
assert(BO->getOpcode() == BO_Assign);
return {BO->getLHS()->IgnoreImpCasts(), BO->getRHS()->IgnoreImpCasts()};
}

const auto *OO = cast<CXXOperatorCallExpr>(AssignExpr);
assert(OO->getOperator() == OO_Equal);

return {OO->getArg(0)->IgnoreImpCasts(), OO->getArg(1)->IgnoreImpCasts()};
std::pair<const Expr *, const Expr *> BinaryArgs =
getBinaryOpArgs(cast<const Expr>(getAssociatedStmt()));
// We want the L-value for each side, so we ignore implicit casts.
return {BinaryArgs.first->IgnoreImpCasts(),
BinaryArgs.second->IgnoreImpCasts(), /*expr=*/nullptr};
}
case OpenACCAtomicKind::Write: {
// Write supports only the format 'x = expr', where the expression is scalar
// type, and 'x' is a scalar l value. As above, this can come in 2 forms;
// Binary Operator or CXXOperatorCallExpr.
std::pair<const Expr *, const Expr *> BinaryArgs =
getBinaryOpArgs(cast<const Expr>(getAssociatedStmt()));
// We want the L-value for ONLY the X side, so we ignore implicit casts. For
// the right side (the expr), we emit it as an r-value so we need to
// maintain implicit casts.
return {/*v=*/nullptr, BinaryArgs.first->IgnoreImpCasts(),
BinaryArgs.second};
}
}

llvm_unreachable("unknown OpenACC atomic kind");
}

OpenACCCacheConstruct *OpenACCCacheConstruct::CreateEmpty(const ASTContext &C,
Expand Down
57 changes: 41 additions & 16 deletions clang/lib/CIR/CodeGen/CIRGenStmtOpenACC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,9 +306,10 @@ CIRGenFunction::emitOpenACCCacheConstruct(const OpenACCCacheConstruct &s) {

mlir::LogicalResult
CIRGenFunction::emitOpenACCAtomicConstruct(const OpenACCAtomicConstruct &s) {
// For now, we are only support 'read', so diagnose. We can switch on the kind
// later once we start implementing the other 3 forms.
if (s.getAtomicKind() != OpenACCAtomicKind::Read) {
// For now, we are only support 'read'/'write', so diagnose. We can switch on
// the kind later once we start implementing the other 3 forms. While we
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// the kind later once we start implementing the other 3 forms. While we
// the kind later once we start implementing the other 2 forms. While we

if (s.getAtomicKind() != OpenACCAtomicKind::Read &&
s.getAtomicKind() != OpenACCAtomicKind::Write) {
cgm.errorNYI(s.getSourceRange(), "OpenACC Atomic Construct");
return mlir::failure();
}
Expand All @@ -318,17 +319,41 @@ CIRGenFunction::emitOpenACCAtomicConstruct(const OpenACCAtomicConstruct &s) {
// it has custom emit logic.
mlir::Location start = getLoc(s.getSourceRange().getBegin());
OpenACCAtomicConstruct::StmtInfo inf = s.getAssociatedStmtInfo();
// Atomic 'read' only permits 'v = x', where v and x are both scalar L values.
// The getAssociatedStmtInfo strips off implicit casts, which includes
// implicit conversions and L-to-R-Value conversions, so we can just emit it
// as an L value. The Flang implementation has no problem with different
// types, so it appears that the dialect can handle the conversions.
mlir::Value v = emitLValue(inf.V).getPointer();
mlir::Value x = emitLValue(inf.X).getPointer();
mlir::Type resTy = convertType(inf.V->getType());
auto op = mlir::acc::AtomicReadOp::create(builder, start, x, v, resTy,
/*ifCond=*/{});
emitOpenACCClauses(op, s.getDirectiveKind(), s.getDirectiveLoc(),
s.clauses());
return mlir::success();

switch (s.getAtomicKind()) {
case OpenACCAtomicKind::None:
case OpenACCAtomicKind::Update:
case OpenACCAtomicKind::Capture:
llvm_unreachable("Unimplemented atomic construct type, should have "
"diagnosed/returned above");
return mlir::failure();
case OpenACCAtomicKind::Read: {

// Atomic 'read' only permits 'v = x', where v and x are both scalar L
// values. The getAssociatedStmtInfo strips off implicit casts, which
// includes implicit conversions and L-to-R-Value conversions, so we can
// just emit it as an L value. The Flang implementation has no problem with
// different types, so it appears that the dialect can handle the
// conversions.
mlir::Value v = emitLValue(inf.V).getPointer();
mlir::Value x = emitLValue(inf.X).getPointer();
mlir::Type resTy = convertType(inf.V->getType());
auto op = mlir::acc::AtomicReadOp::create(builder, start, x, v, resTy,
/*ifCond=*/{});
emitOpenACCClauses(op, s.getDirectiveKind(), s.getDirectiveLoc(),
s.clauses());
return mlir::success();
}
case OpenACCAtomicKind::Write: {
mlir::Value x = emitLValue(inf.X).getPointer();
mlir::Value expr = emitAnyExpr(inf.Expr).getValue();
auto op = mlir::acc::AtomicWriteOp::create(builder, start, x, expr,
/*ifCond=*/{});
emitOpenACCClauses(op, s.getDirectiveKind(), s.getDirectiveLoc(),
s.clauses());
return mlir::success();
}
}

llvm_unreachable("unknown OpenACC atomic kind");
}
55 changes: 55 additions & 0 deletions clang/test/CIR/CodeGenOpenACC/atomic-write.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %clang_cc1 -fopenacc -triple x86_64-linux-gnu -Wno-openacc-self-if-potential-conflict -emit-cir -fclangir -triple x86_64-linux-pc %s -o - | FileCheck %s

extern "C" bool condition(int x, unsigned int y, float f);
extern "C" double do_thing(float f);

struct ConvertsToScalar {
operator float();
};

void use(int x, unsigned int y, float f, ConvertsToScalar cts) {
// CHECK: cir.func{{.*}}(%[[X_ARG:.*]]: !s32i{{.*}}, %[[Y_ARG:.*]]: !u32i{{.*}}, %[[F_ARG:.*]]: !cir.float{{.*}}){{.*}}, %[[CTS_ARG:.*]]: !rec_ConvertsToScalar{{.*}}) {
// CHECK-NEXT: %[[X_ALLOC:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["x", init]
// CHECK-NEXT: %[[Y_ALLOC:.*]] = cir.alloca !u32i, !cir.ptr<!u32i>, ["y", init]
// CHECK-NEXT: %[[F_ALLOC:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["f", init]
// CHECK-NEXT: %[[CTS_ALLOC:.*]] = cir.alloca !rec_ConvertsToScalar, !cir.ptr<!rec_ConvertsToScalar>, ["cts", init]
//
// CHECK-NEXT: cir.store %[[X_ARG]], %[[X_ALLOC]] : !s32i, !cir.ptr<!s32i>
// CHECK-NEXT: cir.store %[[Y_ARG]], %[[Y_ALLOC]] : !u32i, !cir.ptr<!u32i>
// CHECK-NEXT: cir.store %[[F_ARG]], %[[F_ALLOC]] : !cir.float, !cir.ptr<!cir.float>
// CHECK-NEXT: cir.store %[[CTS_ARG]], %[[CTS_ALLOC]] : !rec_ConvertsToScalar, !cir.ptr<!rec_ConvertsToScalar>

// CHECK-NEXT: %[[Y_LOAD:.*]] = cir.load {{.*}}%[[Y_ALLOC]] : !cir.ptr<!u32i>, !u32i
// CHECK-NEXT: %[[Y_TO_FLOAT:.*]] = cir.cast int_to_float %[[Y_LOAD]] : !u32i -> !cir.float
// CHECK-NEXT: %[[F_LOAD:.*]] = cir.load {{.*}}%[[F_ALLOC]] : !cir.ptr<!cir.float>, !cir.float
// CHECK-NEXT: %[[MUL:.*]] = cir.binop(mul, %[[Y_TO_FLOAT]], %[[F_LOAD]]) : !cir.float
// CHECK-NEXT: %[[RHS_CAST:.*]] = cir.cast float_to_int %[[MUL]] : !cir.float -> !s32i
// CHECK-NEXT: acc.atomic.write %[[X_ALLOC]] = %[[RHS_CAST]] : !cir.ptr<!s32i>, !s32i
#pragma acc atomic write
x = y * f;

// CHECK-NEXT: %[[F_LOAD:.*]] = cir.load {{.*}}%[[F_ALLOC]] : !cir.ptr<!cir.float>, !cir.float
// CHECK-NEXT: %[[CALL:.*]] = cir.call @do_thing(%[[F_LOAD]]) : (!cir.float) -> !cir.double
// CHECK-NEXT: %[[CALL_CAST:.*]] = cir.cast float_to_int %[[CALL]] : !cir.double -> !u32i
// CHECK-NEXT: acc.atomic.write %[[Y_ALLOC]] = %[[CALL_CAST]] : !cir.ptr<!u32i>, !u32i
#pragma acc atomic write
y = do_thing(f);

// CHECK-NEXT: %[[X_LOAD:.*]] = cir.load {{.*}}%[[X_ALLOC]] : !cir.ptr<!s32i>, !s32i
// CHECK-NEXT: %[[X_CAST:.*]] = cir.cast int_to_float %[[X_LOAD]] : !s32i -> !cir.float
// CHECK-NEXT: %[[THING_CALL:.*]] = cir.call @do_thing(%[[X_CAST]]) : (!cir.float) -> !cir.double
// CHECK-NEXT: %[[THING_CAST:.*]] = cir.cast floating %[[THING_CALL]] : !cir.double -> !cir.float
// CHECK-NEXT: %[[X_LOAD:.*]] = cir.load {{.*}}%[[X_ALLOC]] : !cir.ptr<!s32i>, !s32i
// CHECK-NEXT: %[[Y_LOAD:.*]] = cir.load {{.*}}%[[Y_ALLOC]] : !cir.ptr<!u32i>, !u32i
// CHECK-NEXT: %[[F_LOAD:.*]] = cir.load {{.*}}%[[F_ALLOC]] : !cir.ptr<!cir.float>, !cir.float
// CHECK-NEXT: %[[COND_CALL:.*]] = cir.call @condition(%[[X_LOAD]], %[[Y_LOAD]], %[[F_LOAD]]) : (!s32i, !u32i, !cir.float) -> !cir.bool
// CHECK-NEXT: %[[COND_CAST:.*]] = builtin.unrealized_conversion_cast %[[COND_CALL]] : !cir.bool to i1
// CHECK-NEXT: acc.atomic.write if(%[[COND_CAST]]) %[[F_ALLOC]] = %[[THING_CAST]] : !cir.ptr<!cir.float>, !cir.float
#pragma acc atomic write if (condition(x, y, f))
f = do_thing(x);

// CHECK-NEXT: %[[CTS_CONV_CALL:.*]] = cir.call @{{.*}}(%[[CTS_ALLOC]]) : (!cir.ptr<!rec_ConvertsToScalar>) -> !cir.float
// CHECK-NEXT: acc.atomic.write %[[F_ALLOC]] = %[[CTS_CONV_CALL]] : !cir.ptr<!cir.float>, !cir.float
#pragma acc atomic write
f = cts;
}
Loading