Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[llvm][mlir][flang][OpenMP] Emit __atomic_load and __atomic_compare_exchange libcalls for complex types in atomic update #92364

Merged
merged 1 commit into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 1 addition & 2 deletions flang/lib/Lower/DirectivesCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,7 @@ static void processOmpAtomicTODO(mlir::Type elementType,
// Based on assertion for supported element types in OMPIRBuilder.cpp
// createAtomicRead
mlir::Type unwrappedEleTy = fir::unwrapRefType(elementType);
bool supportedAtomicType =
(!fir::isa_complex(unwrappedEleTy) && fir::isa_trivial(unwrappedEleTy));
bool supportedAtomicType = fir::isa_trivial(unwrappedEleTy);
tblah marked this conversation as resolved.
Show resolved Hide resolved
if (!supportedAtomicType)
TODO(loc, "Unsupported atomic type");
}
Expand Down
47 changes: 47 additions & 0 deletions flang/test/Integration/OpenMP/atomic-capture-complex.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
!===----------------------------------------------------------------------===!
! 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-llvm -fopenmp %s -o - | FileCheck %s

!CHECK: %[[X_NEW_VAL:.*]] = alloca { float, float }, align 8
!CHECK: %[[VAL_1:.*]] = alloca { float, float }, i64 1, align 8
!CHECK: %[[ORIG_VAL:.*]] = alloca { float, float }, i64 1, align 8
!CHECK: store { float, float } { float 2.000000e+00, float 2.000000e+00 }, ptr %[[ORIG_VAL]], align 4
!CHECK: br label %entry

!CHECK: entry:
!CHECK: %[[ATOMIC_TEMP_LOAD:.*]] = alloca { float, float }, align 8
!CHECK: call void @__atomic_load(i64 8, ptr %[[ORIG_VAL]], ptr %[[ATOMIC_TEMP_LOAD]], i32 0)
!CHECK: %[[PHI_NODE_ENTRY_1:.*]] = load { float, float }, ptr %[[ATOMIC_TEMP_LOAD]], align 8
!CHECK: br label %.atomic.cont

!CHECK: .atomic.cont
!CHECK: %[[VAL_4:.*]] = phi { float, float } [ %[[PHI_NODE_ENTRY_1]], %entry ], [ %{{.*}}, %.atomic.cont ]
!CHECK: %[[VAL_5:.*]] = extractvalue { float, float } %[[VAL_4]], 0
!CHECK: %[[VAL_6:.*]] = extractvalue { float, float } %[[VAL_4]], 1
!CHECK: %[[VAL_7:.*]] = fadd contract float %[[VAL_5]], 1.000000e+00
!CHECK: %[[VAL_8:.*]] = fadd contract float %[[VAL_6]], 1.000000e+00
!CHECK: %[[VAL_9:.*]] = insertvalue { float, float } undef, float %[[VAL_7]], 0
!CHECK: %[[VAL_10:.*]] = insertvalue { float, float } %[[VAL_9]], float %[[VAL_8]], 1
!CHECK: store { float, float } %[[VAL_10]], ptr %[[X_NEW_VAL]], align 4
!CHECK: %[[VAL_11:.*]] = call i1 @__atomic_compare_exchange(i64 8, ptr %[[ORIG_VAL]], ptr %[[ATOMIC_TEMP_LOAD]], ptr %[[X_NEW_VAL]],
!i32 2, i32 2)
!CHECK: %[[VAL_12:.*]] = load { float, float }, ptr %[[ATOMIC_TEMP_LOAD]], align 4
!CHECK: br i1 %[[VAL_11]], label %.atomic.exit, label %.atomic.cont

!CHECK: .atomic.exit
!CHECK: store { float, float } %[[VAL_10]], ptr %[[VAL_1]], align 4

program main
complex*8 ia, ib
ia = (2, 2)
!$omp atomic capture
ia = ia + (1, 1)
ib = ia
!$omp end atomic
end program
42 changes: 42 additions & 0 deletions flang/test/Integration/OpenMP/atomic-update-complex.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
!===----------------------------------------------------------------------===!
! 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-llvm -fopenmp %s -o - | FileCheck %s

!CHECK: define void @_QQmain() {
!CHECK: %[[X_NEW_VAL:.*]] = alloca { float, float }, align 8
!CHECK: {{.*}} = alloca { float, float }, i64 1, align 8
!CHECK: %[[ORIG_VAL:.*]] = alloca { float, float }, i64 1, align 8
!CHECK: store { float, float } { float 2.000000e+00, float 2.000000e+00 }, ptr %[[ORIG_VAL]], align 4
!CHECK: br label %entry

!CHECK: entry:
!CHECK: %[[ATOMIC_TEMP_LOAD:.*]] = alloca { float, float }, align 8
!CHECK: call void @__atomic_load(i64 8, ptr %[[ORIG_VAL]], ptr %[[ATOMIC_TEMP_LOAD]], i32 0)
!CHECK: %[[PHI_NODE_ENTRY_1:.*]] = load { float, float }, ptr %[[ATOMIC_TEMP_LOAD]], align 8
!CHECK: br label %.atomic.cont

!CHECK: .atomic.cont
!CHECK: %[[VAL_4:.*]] = phi { float, float } [ %[[PHI_NODE_ENTRY_1]], %entry ], [ %{{.*}}, %.atomic.cont ]
!CHECK: %[[VAL_5:.*]] = extractvalue { float, float } %[[VAL_4]], 0
!CHECK: %[[VAL_6:.*]] = extractvalue { float, float } %[[VAL_4]], 1
!CHECK: %[[VAL_7:.*]] = fadd contract float %[[VAL_5]], 1.000000e+00
!CHECK: %[[VAL_8:.*]] = fadd contract float %[[VAL_6]], 1.000000e+00
!CHECK: %[[VAL_9:.*]] = insertvalue { float, float } undef, float %[[VAL_7]], 0
!CHECK: %[[VAL_10:.*]] = insertvalue { float, float } %[[VAL_9]], float %[[VAL_8]], 1
!CHECK: store { float, float } %[[VAL_10]], ptr %[[X_NEW_VAL]], align 4
!CHECK: %[[VAL_11:.*]] = call i1 @__atomic_compare_exchange(i64 8, ptr %[[ORIG_VAL]], ptr %[[ATOMIC_TEMP_LOAD]], ptr %[[X_NEW_VAL]], i32 2, i32 2)
!CHECK: %[[VAL_12:.*]] = load { float, float }, ptr %[[ATOMIC_TEMP_LOAD]], align 4
!CHECK: br i1 %[[VAL_11]], label %.atomic.exit, label %.atomic.cont
program main
complex*8 ia, ib
ia = (2, 2)
!$omp atomic update
ia = ia + (1, 1)
!$omp end atomic
end program
8 changes: 0 additions & 8 deletions flang/test/Lower/OpenMP/Todo/atomic-complex.f90

This file was deleted.

232 changes: 232 additions & 0 deletions llvm/include/llvm/Frontend/Atomic/Atomic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
//===--- Atomic.h - Codegen of atomic operations
//---------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_FRONTEND_ATOMIC_ATOMIC_H
#define LLVM_FRONTEND_ATOMIC_ATOMIC_H

#include "llvm/ADT/DenseMap.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Operator.h"
#include "llvm/IR/RuntimeLibcalls.h"

namespace llvm {

template <typename IRBuilderTy> struct AtomicInfo {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is not necessary to template over IRBuilderTy, you should be using IRBuilderBase instead.


IRBuilderTy *Builder;
Type *Ty;
uint64_t AtomicSizeInBits;
uint64_t ValueSizeInBits;
llvm::Align AtomicAlign;
llvm::Align ValueAlign;
Copy link
Contributor

Choose a reason for hiding this comment

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

You should not be using llvm:: prefixes inside namespace llvm {}.

bool UseLibcall;

public:
AtomicInfo(IRBuilderTy *Builder, Type *Ty, uint64_t AtomicSizeInBits,
uint64_t ValueSizeInBits, llvm::Align AtomicAlign,
llvm::Align ValueAlign, bool UseLibcall)
: Builder(Builder), Ty(Ty), AtomicSizeInBits(AtomicSizeInBits),
ValueSizeInBits(ValueSizeInBits), AtomicAlign(AtomicAlign),
ValueAlign(ValueAlign), UseLibcall(UseLibcall) {}

virtual ~AtomicInfo() = default;

llvm::Align getAtomicAlignment() const { return AtomicAlign; }
uint64_t getAtomicSizeInBits() const { return AtomicSizeInBits; }
uint64_t getValueSizeInBits() const { return ValueSizeInBits; }
bool shouldUseLibcall() const { return UseLibcall; }
llvm::Type *getAtomicTy() const { return Ty; }

virtual llvm::Value *getAtomicPointer() const = 0;
virtual void decorateWithTBAA(Instruction *I) = 0;
virtual llvm::AllocaInst *CreateAlloca(llvm::Type *Ty,
const llvm::Twine &Name) const = 0;

/*
* Is the atomic size larger than the underlying value type?
* Note that the absence of padding does not mean that atomic
* objects are completely interchangeable with non-atomic
* objects: we might have promoted the alignment of a type
* without making it bigger.
*/
bool hasPadding() const { return (ValueSizeInBits != AtomicSizeInBits); }

LLVMContext &getLLVMContext() const { return Builder->getContext(); }

static bool shouldCastToInt(llvm::Type *ValTy, bool CmpXchg) {
if (ValTy->isFloatingPointTy())
return ValTy->isX86_FP80Ty() || CmpXchg;
return !ValTy->isIntegerTy() && !ValTy->isPointerTy();
}

llvm::Value *EmitAtomicLoadOp(llvm::AtomicOrdering AO, bool IsVolatile,
bool CmpXchg = false) {
Value *Ptr = getAtomicPointer();
Type *AtomicTy = Ty;
if (shouldCastToInt(Ty, CmpXchg))
AtomicTy = llvm::IntegerType::get(getLLVMContext(), AtomicSizeInBits);
LoadInst *Load =
Builder->CreateAlignedLoad(AtomicTy, Ptr, AtomicAlign, "atomic-load");
Load->setAtomic(AO);
if (IsVolatile)
Load->setVolatile(true);
decorateWithTBAA(Load);
return Load;
}

static CallInst *EmitAtomicLibcall(IRBuilderTy *Builder, StringRef fnName,
Type *ResultType, ArrayRef<Value *> Args) {
LLVMContext &ctx = Builder->getContext();
SmallVector<Type *, 6> ArgTys;
for (Value *Arg : Args)
ArgTys.push_back(Arg->getType());
FunctionType *FnType = FunctionType::get(ResultType, ArgTys, false);
Module *M = Builder->GetInsertBlock()->getModule();

// TODO: Use llvm::TargetLowering for Libcall ABI
llvm::AttrBuilder fnAttrBuilder(ctx);
fnAttrBuilder.addAttribute(llvm::Attribute::NoUnwind);
fnAttrBuilder.addAttribute(llvm::Attribute::WillReturn);
llvm::AttributeList fnAttrs = llvm::AttributeList::get(
ctx, llvm::AttributeList::FunctionIndex, fnAttrBuilder);
FunctionCallee LibcallFn = M->getOrInsertFunction(fnName, FnType, fnAttrs);
CallInst *Call = Builder->CreateCall(LibcallFn, Args);
return Call;
}

llvm::Value *getAtomicSizeValue() const {
LLVMContext &ctx = getLLVMContext();

// TODO: Get from llvm::TargetMachine / clang::TargetInfo
// if clang shares this codegen in future
constexpr uint16_t SizeTBits = 64;
constexpr uint16_t BitsPerByte = 8;
return llvm::ConstantInt::get(llvm::IntegerType::get(ctx, SizeTBits),
AtomicSizeInBits / BitsPerByte);
}

std::pair<llvm::Value *, llvm::Value *> EmitAtomicCompareExchangeLibcall(
llvm::Value *ExpectedVal, llvm::Value *DesiredVal,
llvm::AtomicOrdering Success, llvm::AtomicOrdering Failure) {
LLVMContext &ctx = getLLVMContext();

// __atomic_compare_exchange's expected and desired are passed by pointers
// FIXME: types

// TODO: Get from llvm::TargetMachine / clang::TargetInfo
// if clang shares this codegen in future
constexpr uint64_t IntBits = 32;

// bool __atomic_compare_exchange(size_t size, void *obj, void *expected,
// void *desired, int success, int failure);
llvm::Value *Args[6] = {
getAtomicSizeValue(),
getAtomicPointer(),
ExpectedVal,
DesiredVal,
llvm::Constant::getIntegerValue(
llvm::IntegerType::get(ctx, IntBits),
llvm::APInt(IntBits, static_cast<uint64_t>(Success),
/*signed=*/true)),
llvm::Constant::getIntegerValue(
llvm::IntegerType::get(ctx, IntBits),
llvm::APInt(IntBits, static_cast<uint64_t>(Failure),
/*signed=*/true)),
};
auto Result = EmitAtomicLibcall(Builder, "__atomic_compare_exchange",
llvm::IntegerType::getInt1Ty(ctx), Args);
return std::make_pair(ExpectedVal, Result);
}

Value *castToAtomicIntPointer(Value *addr) const {
return addr; // opaque pointer
}

Value *getAtomicAddressAsAtomicIntPointer() const {
return castToAtomicIntPointer(getAtomicPointer());
}

std::pair<llvm::Value *, llvm::Value *>
EmitAtomicCompareExchangeOp(llvm::Value *ExpectedVal, llvm::Value *DesiredVal,
llvm::AtomicOrdering Success,
llvm::AtomicOrdering Failure,
bool IsVolatile = false, bool IsWeak = false) {
// Do the atomic store.
Value *Addr = getAtomicAddressAsAtomicIntPointer();
auto *Inst = Builder->CreateAtomicCmpXchg(Addr, ExpectedVal, DesiredVal,
getAtomicAlignment(), Success,
Failure, llvm::SyncScope::System);
// Other decoration.
Inst->setVolatile(IsVolatile);
Inst->setWeak(IsWeak);

auto *PreviousVal = Builder->CreateExtractValue(Inst, /*Idxs=*/0);
auto *SuccessFailureVal = Builder->CreateExtractValue(Inst, /*Idxs=*/1);
return std::make_pair(PreviousVal, SuccessFailureVal);
}

std::pair<llvm::Value *, llvm::Value *>
EmitAtomicCompareExchange(llvm::Value *ExpectedVal, llvm::Value *DesiredVal,
llvm::AtomicOrdering Success,
llvm::AtomicOrdering Failure, bool IsVolatile,
bool IsWeak) {
if (shouldUseLibcall())
return EmitAtomicCompareExchangeLibcall(ExpectedVal, DesiredVal, Success,
Failure);

auto Res = EmitAtomicCompareExchangeOp(ExpectedVal, DesiredVal, Success,
Failure, IsVolatile, IsWeak);
return Res;
}

// void __atomic_load(size_t size, void *mem, void *return, int order);
std::pair<llvm::LoadInst *, llvm::AllocaInst *>
EmitAtomicLoadLibcall(llvm::AtomicOrdering AO) {
LLVMContext &Ctx = getLLVMContext();
Type *SizedIntTy = Type::getIntNTy(Ctx, getAtomicSizeInBits());
Type *ResultTy;
SmallVector<Value *, 6> Args;
AttributeList Attr;
Module *M = Builder->GetInsertBlock()->getModule();
const DataLayout &DL = M->getDataLayout();
Args.push_back(ConstantInt::get(DL.getIntPtrType(Ctx),
this->getAtomicSizeInBits() / 8));

Value *PtrVal = getAtomicPointer();
PtrVal = Builder->CreateAddrSpaceCast(PtrVal, PointerType::getUnqual(Ctx));
Args.push_back(PtrVal);
AllocaInst *AllocaResult =
CreateAlloca(Ty, getAtomicPointer()->getName() + "atomic.temp.load");
const Align AllocaAlignment = DL.getPrefTypeAlign(SizedIntTy);
AllocaResult->setAlignment(AllocaAlignment);
Args.push_back(AllocaResult);
Constant *OrderingVal =
ConstantInt::get(Type::getInt32Ty(Ctx), (int)toCABI(AO));
Args.push_back(OrderingVal);

ResultTy = Type::getVoidTy(Ctx);
SmallVector<Type *, 6> ArgTys;
for (Value *Arg : Args)
ArgTys.push_back(Arg->getType());
FunctionType *FnType = FunctionType::get(ResultTy, ArgTys, false);
FunctionCallee LibcallFn =
M->getOrInsertFunction("__atomic_load", FnType, Attr);
CallInst *Call = Builder->CreateCall(LibcallFn, Args);
Call->setAttributes(Attr);
return std::make_pair(
Builder->CreateAlignedLoad(Ty, AllocaResult, AllocaAlignment),
AllocaResult);
}
};
} // end namespace llvm

#endif /* LLVM_FRONTEND_ATOMIC_ATOMIC_H */
31 changes: 31 additions & 0 deletions llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define LLVM_FRONTEND_OPENMP_OMPIRBUILDER_H

#include "llvm/Analysis/MemorySSAUpdater.h"
#include "llvm/Frontend/Atomic/Atomic.h"
#include "llvm/Frontend/OpenMP/OMPConstants.h"
#include "llvm/Frontend/OpenMP/OMPGridValues.h"
#include "llvm/IR/DebugLoc.h"
Expand Down Expand Up @@ -479,6 +480,27 @@ class OpenMPIRBuilder {
T(Triple(M.getTargetTriple())) {}
~OpenMPIRBuilder();

class AtomicInfo : public llvm::AtomicInfo<IRBuilder<>> {
llvm::Value *AtomicVar;

public:
AtomicInfo(IRBuilder<> *Builder, llvm::Type *Ty, uint64_t AtomicSizeInBits,
uint64_t ValueSizeInBits, llvm::Align AtomicAlign,
llvm::Align ValueAlign, bool UseLibcall, llvm::Value *AtomicVar)
: llvm::AtomicInfo<IRBuilder<>>(Builder, Ty, AtomicSizeInBits,
ValueSizeInBits, AtomicAlign,
ValueAlign, UseLibcall),
AtomicVar(AtomicVar) {}

llvm::Value *getAtomicPointer() const override { return AtomicVar; }
void decorateWithTBAA(llvm::Instruction *I) override {}
llvm::AllocaInst *CreateAlloca(llvm::Type *Ty,
const llvm::Twine &Name) const override {
llvm::AllocaInst *allocaInst = Builder->CreateAlloca(Ty);
allocaInst->setName(Name);
return allocaInst;
}
};
/// Initialize the internal state, this will put structures types and
/// potentially other helpers into the underlying module. Must be called
/// before any other method and only once! This internal state includes types
Expand Down Expand Up @@ -3039,6 +3061,15 @@ class OpenMPIRBuilder {
AtomicUpdateCallbackTy &UpdateOp, bool VolatileX,
bool IsXBinopExpr);

std::pair<llvm::LoadInst *, llvm::AllocaInst *>
EmitAtomicLoadLibcall(Value *X, Type *XElemTy, llvm::AtomicOrdering AO,
uint64_t AtomicSizeInBits);

std::pair<llvm::Value *, llvm::Value *> EmitAtomicCompareExchangeLibcall(
Value *X, Type *XElemTy, uint64_t AtomicSizeInBits,
llvm::Value *ExpectedVal, llvm::Value *DesiredVal,
llvm::AtomicOrdering Success, llvm::AtomicOrdering Failure);

/// Emit the binary op. described by \p RMWOp, using \p Src1 and \p Src2 .
///
/// \Return The instruction
Expand Down
Loading
Loading