Skip to content

Commit

Permalink
[CIR][CIRGen] Support for builtin __atomic_thread_fence
Browse files Browse the repository at this point in the history
Resolves #1274

Implements atomic thread fence synchronization primitive
corresponding to `atomic.thread_fence` CIR.
  • Loading branch information
Rajveer100 committed Feb 6, 2025
1 parent d329c96 commit 97fb189
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 1 deletion.
41 changes: 41 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -5409,6 +5409,47 @@ def AtomicCmpXchg : CIR_Op<"atomic.cmp_xchg",
let hasVerifier = 0;
}

def MemScope_SingleThread : I32EnumAttrCase<"MemScope_SingleThread",
0, "single_thread">;
def MemScope_System : I32EnumAttrCase<"MemScope_System",
1, "system">;

def MemScopeKind : I32EnumAttr<
"MemScopeKind",
"Memory Scope Enumeration",
[MemScope_SingleThread, MemScope_System]> {
let cppNamespace = "::cir";
}

def AtomicFence : CIR_Op<"atomic.fence"> {
let summary = "Atomic thread fence";
let description = [{
C/C++ Atomic thread fence synchronization primitive. Implements the builtin
`__atomic_thread_fence` which enforces memory ordering constraints across
threads within the specified synchronization scope.

This handles all variations including:
- `__atomic_thread_fence`
- `__atomic_signal_fence`
- `__c11_atomic_thread_fence`
- `__c11_atomic_signal_fence`

Example:


}];
let results = (outs);
let arguments = (ins Arg<MemScopeKind, "sync scope">:$sync_scope,
Arg<MemOrder, "memory order">:$ordering);

let assemblyFormat = [{
`(` `sync_scope` `=` $sync_scope `,`
`ordering` `=` $ordering `)` attr-dict
}];

let hasVerifier = 0;
}

def SignBitOp : CIR_Op<"signbit", [Pure]> {
let summary = "Checks the sign of a floating-point number";
let description = [{
Expand Down
36 changes: 35 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
#include "CIRGenCstEmitter.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "CIRGenValue.h"
#include "TargetInfo.h"
#include "clang/AST/Expr.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/MissingFeatures.h"

// TODO(cir): we shouldn't need this but we currently reuse intrinsic IDs for
Expand All @@ -30,7 +34,9 @@
#include "clang/Frontend/FrontendDiagnostic.h"

#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Value.h"
#include "mlir/Support/LLVM.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "llvm/Support/ErrorHandling.h"

Expand Down Expand Up @@ -330,6 +336,30 @@ static mlir::Value MakeAtomicCmpXchgValue(CIRGenFunction &cgf,
return returnBool ? op.getResult(1) : op.getResult(0);
}

static mlir::Value MakeAtomicFenceValue(CIRGenFunction &cgf,
const CallExpr *expr,
cir::MemScopeKind syncScope) {
auto &builder = cgf.getBuilder();
mlir::Value orderingVal = cgf.emitScalarExpr(expr->getArg(0));

auto constOrdering =
mlir::dyn_cast<cir::ConstantOp>(orderingVal.getDefiningOp());
if (!constOrdering)
llvm_unreachable("NYI: variable ordering not supported");

auto constOrderingAttr =
mlir::dyn_cast<cir::IntAttr>(constOrdering.getValue());
if (!constOrderingAttr) {
cir::MemOrder ordering =
static_cast<cir::MemOrder>(constOrderingAttr.getUInt());

builder.create<cir::AtomicFence>(cgf.getLoc(expr->getSourceRange()),
syncScope, ordering);
}

return mlir::Value();
}

static bool
typeRequiresBuiltinLaunderImp(const ASTContext &astContext, QualType ty,
llvm::SmallPtrSetImpl<const Decl *> &seen) {
Expand Down Expand Up @@ -1840,10 +1870,14 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
llvm_unreachable("BI__atomic_clear NYI");

case Builtin::BI__atomic_thread_fence:
return RValue::get(
MakeAtomicFenceValue(*this, E, cir::MemScopeKind::MemScope_System));
case Builtin::BI__atomic_signal_fence:
return RValue::get(MakeAtomicFenceValue(
*this, E, cir::MemScopeKind::MemScope_SingleThread));
case Builtin::BI__c11_atomic_thread_fence:
case Builtin::BI__c11_atomic_signal_fence:
llvm_unreachable("BI__atomic_thread_fence like NYI");
llvm_unreachable("BI__c11_atomic_thread_fence like NYI");

case Builtin::BI__builtin_signbit:
case Builtin::BI__builtin_signbitf:
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "mlir/Conversion/SCFToControlFlow/SCFToControlFlow.h"
#include "mlir/Dialect/DLTI/DLTI.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
#include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Builders.h"
Expand All @@ -41,6 +42,8 @@
#include "mlir/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.h"
#include "mlir/Target/LLVMIR/Export.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/Passes.h"
#include "clang/CIR/LoweringHelpers.h"
#include "clang/CIR/MissingFeatures.h"
Expand Down Expand Up @@ -3399,6 +3402,12 @@ mlir::LogicalResult CIRToLLVMAtomicFetchLowering::matchAndRewrite(
return mlir::success();
}

// mlir::LogicalResult CIRToLLVMAtomicFenceLowering::matchAndRewrite(
// cir::AtomicFence op, OpAdaptor adaptor,
// mlir::ConversionPatternRewriter &rewriter) const {
// return mlir::success();
// }

mlir::LogicalResult CIRToLLVMByteswapOpLowering::matchAndRewrite(
cir::ByteswapOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "mlir/IR/MLIRContext.h"
#include "mlir/Interfaces/DataLayoutInterfaces.h"
#include "mlir/Transforms/DialectConversion.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"

namespace cir {
namespace direct {
Expand Down Expand Up @@ -820,6 +821,16 @@ class CIRToLLVMAtomicFetchLowering
mlir::ConversionPatternRewriter &) const override;
};

// class CIRToLLVMAtomicFenceLowering
// : public mlir::OpConversionPattern<cir::AtomicFence> {
// public:
// using mlir::OpConversionPattern<cir::AtomicFence>::OpConversionPattern;
//
// mlir::LogicalResult
// matchAndRewrite(cir::AtomicFence op, OpAdaptor,
// mlir::ConversionPatternRewriter &) const override;
// };

class CIRToLLVMByteswapOpLowering
: public mlir::OpConversionPattern<cir::ByteswapOp> {
public:
Expand Down
93 changes: 93 additions & 0 deletions clang/test/CIR/CodeGen/atomic-thread-fence.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// UN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
// UN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s


struct Data {
int value;
void *ptr;
};

typedef struct Data *DataPtr;

void applyThreadFence() {
__atomic_thread_fence(5);
}

// CIR-LABEL: cir.func no_proto @applyThreadFence
// CIR: %0 = cir.const #cir.int<5> : !s32i
// CIR: cir.return

void applySignalFence() {
__atomic_signal_fence(5);
}
// CIR-LABEL: cir.func no_proto @applySignalFence
// CIR: %0 = cir.const #cir.int<5> : !s32i
// CIR: cir.return

void modifyWithThreadFence(DataPtr d) {
__atomic_thread_fence(5);
d->value = 42;
}
// CIR-LABEL: cir.func @modifyWithThreadFence
// CIR: %0 = cir.alloca !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>, ["d", init] {alignment = 8 : i64}
// CIR: cir.store %arg0, %0 : !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>
// CIR: %1 = cir.const #cir.int<5> : !s32i
// CIR: %2 = cir.const #cir.int<42> : !s32i
// CIR: %3 = cir.load %0 : !cir.ptr<!cir.ptr<!ty_Data>>, !cir.ptr<!ty_Data>
// CIR: %4 = cir.get_member %3[0] {name = "value"} : !cir.ptr<!ty_Data> -> !cir.ptr<!s32i>
// CIR: cir.store %2, %4 : !s32i, !cir.ptr<!s32i>
// CIR: cir.return

void modifyWithSignalFence(DataPtr d) {
__atomic_signal_fence(5);
d->value = 24;
}
// CIR-LABEL: cir.func @modifyWithSignalFence
// %0 = cir.alloca !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>, ["d", init] {alignment = 8 : i64}
// cir.store %arg0, %0 : !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>
// %1 = cir.const #cir.int<5> : !s32i
// %2 = cir.const #cir.int<24> : !s32i
// %3 = cir.load %0 : !cir.ptr<!cir.ptr<!ty_Data>>, !cir.ptr<!ty_Data>
// %4 = cir.get_member %3[0] {name = "value"} : !cir.ptr<!ty_Data> -> !cir.ptr<!s32i>
// cir.store %2, %4 : !s32i, !cir.ptr<!s32i>
// cir.return

void loadWithThreadFence(DataPtr d) {
__atomic_thread_fence(5);
__atomic_load_n(&d->ptr, 5);
}
// CIR-LABEL: cir.func @loadWithThreadFence
// %0 = cir.alloca !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>, ["d", init] {alignment = 8 : i64}
// %1 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["atomic-temp"] {alignment = 8 : i64}
// cir.store %arg0, %0 : !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>
// %2 = cir.const #cir.int<5> : !s32i
// %3 = cir.load %0 : !cir.ptr<!cir.ptr<!ty_Data>>, !cir.ptr<!ty_Data>
// %4 = cir.get_member %3[1] {name = "ptr"} : !cir.ptr<!ty_Data> -> !cir.ptr<!cir.ptr<!void>>
// %5 = cir.const #cir.int<5> : !s32i
// %6 = cir.cast(bitcast, %4 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!u64i>
// %7 = cir.load atomic(seq_cst) %6 : !cir.ptr<!u64i>, !u64i
// %8 = cir.cast(bitcast, %1 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!u64i>
// cir.store %7, %8 : !u64i, !cir.ptr<!u64i>
// %9 = cir.load %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
// cir.return

void loadWithSignalFence(DataPtr d) {
__atomic_signal_fence(5);
__atomic_load_n(&d->ptr, 5);
}
// CIR-LABEL: cir.func @loadWithSignalFence
// %0 = cir.alloca !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>, ["d", init] {alignment = 8 : i64}
// %1 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["atomic-temp"] {alignment = 8 : i64}
// cir.store %arg0, %0 : !cir.ptr<!ty_Data>, !cir.ptr<!cir.ptr<!ty_Data>>
// %2 = cir.const #cir.int<5> : !s32i
// %3 = cir.load %0 : !cir.ptr<!cir.ptr<!ty_Data>>, !cir.ptr<!ty_Data>
// %4 = cir.get_member %3[1] {name = "ptr"} : !cir.ptr<!ty_Data> -> !cir.ptr<!cir.ptr<!void>>
// %5 = cir.const #cir.int<5> : !s32i
// %6 = cir.cast(bitcast, %4 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!u64i>
// %7 = cir.load atomic(seq_cst) %6 : !cir.ptr<!u64i>, !u64i
// %8 = cir.cast(bitcast, %1 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!u64i>
// cir.store %7, %8 : !u64i, !cir.ptr<!u64i>
// %9 = cir.load %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void>
// cir.return

0 comments on commit 97fb189

Please sign in to comment.