blob: e4e5c718c16b332190017ae718327d4e373826b9 [file] [log] [blame]
//===--- CIRGenAtomic.cpp - Emit CIR for 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
//
//===----------------------------------------------------------------------===//
//
// This file contains the code for emitting atomic operations.
//
//===----------------------------------------------------------------------===//
#include "Address.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "CIRGenOpenMPRuntime.h"
#include "TargetInfo.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDataLayout.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"
#include "clang/CodeGen/CGFunctionInfo.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "llvm/Support/ErrorHandling.h"
#include <cstdint>
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
using namespace cir;
using namespace clang;
namespace {
class AtomicInfo {
CIRGenFunction &CGF;
QualType AtomicTy;
QualType ValueTy;
uint64_t AtomicSizeInBits;
uint64_t ValueSizeInBits;
CharUnits AtomicAlign;
CharUnits ValueAlign;
TypeEvaluationKind EvaluationKind;
bool UseLibcall;
LValue LVal;
CIRGenBitFieldInfo BFI;
mlir::Location loc;
public:
AtomicInfo(CIRGenFunction &CGF, LValue &lvalue, mlir::Location l)
: CGF(CGF), AtomicSizeInBits(0), ValueSizeInBits(0),
EvaluationKind(TEK_Scalar), UseLibcall(true), loc(l) {
assert(!lvalue.isGlobalReg());
ASTContext &C = CGF.getContext();
if (lvalue.isSimple()) {
AtomicTy = lvalue.getType();
if (auto *ATy = AtomicTy->getAs<AtomicType>())
ValueTy = ATy->getValueType();
else
ValueTy = AtomicTy;
EvaluationKind = CGF.getEvaluationKind(ValueTy);
uint64_t ValueAlignInBits;
uint64_t AtomicAlignInBits;
TypeInfo ValueTI = C.getTypeInfo(ValueTy);
ValueSizeInBits = ValueTI.Width;
ValueAlignInBits = ValueTI.Align;
TypeInfo AtomicTI = C.getTypeInfo(AtomicTy);
AtomicSizeInBits = AtomicTI.Width;
AtomicAlignInBits = AtomicTI.Align;
assert(ValueSizeInBits <= AtomicSizeInBits);
assert(ValueAlignInBits <= AtomicAlignInBits);
AtomicAlign = C.toCharUnitsFromBits(AtomicAlignInBits);
ValueAlign = C.toCharUnitsFromBits(ValueAlignInBits);
if (lvalue.getAlignment().isZero())
lvalue.setAlignment(AtomicAlign);
LVal = lvalue;
} else if (lvalue.isBitField()) {
llvm_unreachable("NYI");
} else if (lvalue.isVectorElt()) {
ValueTy = lvalue.getType()->castAs<VectorType>()->getElementType();
ValueSizeInBits = C.getTypeSize(ValueTy);
AtomicTy = lvalue.getType();
AtomicSizeInBits = C.getTypeSize(AtomicTy);
AtomicAlign = ValueAlign = lvalue.getAlignment();
LVal = lvalue;
} else {
llvm_unreachable("NYI");
}
UseLibcall = !C.getTargetInfo().hasBuiltinAtomic(
AtomicSizeInBits, C.toBits(lvalue.getAlignment()));
}
QualType getAtomicType() const { return AtomicTy; }
QualType getValueType() const { return ValueTy; }
CharUnits getAtomicAlignment() const { return AtomicAlign; }
uint64_t getAtomicSizeInBits() const { return AtomicSizeInBits; }
uint64_t getValueSizeInBits() const { return ValueSizeInBits; }
TypeEvaluationKind getEvaluationKind() const { return EvaluationKind; }
bool shouldUseLibcall() const { return UseLibcall; }
const LValue &getAtomicLValue() const { return LVal; }
mlir::Value getAtomicPointer() const {
if (LVal.isSimple())
return LVal.getPointer();
else if (LVal.isBitField())
return LVal.getBitFieldPointer();
else if (LVal.isVectorElt())
return LVal.getVectorPointer();
assert(LVal.isExtVectorElt());
// TODO(cir): return LVal.getExtVectorPointer();
llvm_unreachable("NYI");
}
Address getAtomicAddress() const {
mlir::Type ElTy;
if (LVal.isSimple())
ElTy = LVal.getAddress().getElementType();
else if (LVal.isBitField())
ElTy = LVal.getBitFieldAddress().getElementType();
else if (LVal.isVectorElt())
ElTy = LVal.getVectorAddress().getElementType();
else // TODO(cir): ElTy = LVal.getExtVectorAddress().getElementType();
llvm_unreachable("NYI");
return Address(getAtomicPointer(), ElTy, getAtomicAlignment());
}
Address getAtomicAddressAsAtomicIntPointer() const {
return castToAtomicIntPointer(getAtomicAddress());
}
/// 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); }
bool emitMemSetZeroIfNecessary() const;
mlir::Value getAtomicSizeValue() const { llvm_unreachable("NYI"); }
mlir::Value getScalarRValValueOrNull(RValue RVal) const;
/// Cast the given pointer to an integer pointer suitable for atomic
/// operations if the source.
Address castToAtomicIntPointer(Address Addr) const;
/// If Addr is compatible with the iN that will be used for an atomic
/// operation, bitcast it. Otherwise, create a temporary that is suitable
/// and copy the value across.
Address convertToAtomicIntPointer(Address Addr) const;
/// Turn an atomic-layout object into an r-value.
RValue convertAtomicTempToRValue(Address addr, AggValueSlot resultSlot,
SourceLocation loc, bool AsValue) const;
/// Converts a rvalue to integer value.
mlir::Value convertRValueToInt(RValue RVal, bool CmpXchg = false) const;
RValue ConvertIntToValueOrAtomic(mlir::Value IntVal, AggValueSlot ResultSlot,
SourceLocation Loc, bool AsValue) const;
/// Copy an atomic r-value into atomic-layout memory.
void emitCopyIntoMemory(RValue rvalue) const;
/// Project an l-value down to the value field.
LValue projectValue() const {
assert(LVal.isSimple());
Address addr = getAtomicAddress();
if (hasPadding())
llvm_unreachable("NYI");
return LValue::makeAddr(addr, getValueType(), CGF.getContext(),
LVal.getBaseInfo());
}
/// Emits atomic load.
/// \returns Loaded value.
RValue EmitAtomicLoad(AggValueSlot ResultSlot, SourceLocation Loc,
bool AsValue, llvm::AtomicOrdering AO, bool IsVolatile);
/// Emits atomic compare-and-exchange sequence.
/// \param Expected Expected value.
/// \param Desired Desired value.
/// \param Success Atomic ordering for success operation.
/// \param Failure Atomic ordering for failed operation.
/// \param IsWeak true if atomic operation is weak, false otherwise.
/// \returns Pair of values: previous value from storage (value type) and
/// boolean flag (i1 type) with true if success and false otherwise.
std::pair<RValue, mlir::Value>
EmitAtomicCompareExchange(RValue Expected, RValue Desired,
llvm::AtomicOrdering Success =
llvm::AtomicOrdering::SequentiallyConsistent,
llvm::AtomicOrdering Failure =
llvm::AtomicOrdering::SequentiallyConsistent,
bool IsWeak = false);
/// Emits atomic update.
/// \param AO Atomic ordering.
/// \param UpdateOp Update operation for the current lvalue.
void EmitAtomicUpdate(llvm::AtomicOrdering AO,
const llvm::function_ref<RValue(RValue)> &UpdateOp,
bool IsVolatile);
/// Emits atomic update.
/// \param AO Atomic ordering.
void EmitAtomicUpdate(llvm::AtomicOrdering AO, RValue UpdateRVal,
bool IsVolatile);
/// Materialize an atomic r-value in atomic-layout memory.
Address materializeRValue(RValue rvalue) const;
/// Creates temp alloca for intermediate operations on atomic value.
Address CreateTempAlloca() const;
private:
bool requiresMemSetZero(mlir::Type ty) const;
/// Emits atomic load as a libcall.
void EmitAtomicLoadLibcall(mlir::Value AddForLoaded, llvm::AtomicOrdering AO,
bool IsVolatile);
/// Emits atomic load as LLVM instruction.
mlir::Value EmitAtomicLoadOp(llvm::AtomicOrdering AO, bool IsVolatile);
/// Emits atomic compare-and-exchange op as a libcall.
mlir::Value EmitAtomicCompareExchangeLibcall(
mlir::Value ExpectedAddr, mlir::Value DesiredAddr,
llvm::AtomicOrdering Success =
llvm::AtomicOrdering::SequentiallyConsistent,
llvm::AtomicOrdering Failure =
llvm::AtomicOrdering::SequentiallyConsistent);
/// Emits atomic compare-and-exchange op as LLVM instruction.
std::pair<mlir::Value, mlir::Value>
EmitAtomicCompareExchangeOp(mlir::Value ExpectedVal, mlir::Value DesiredVal,
llvm::AtomicOrdering Success =
llvm::AtomicOrdering::SequentiallyConsistent,
llvm::AtomicOrdering Failure =
llvm::AtomicOrdering::SequentiallyConsistent,
bool IsWeak = false);
/// Emit atomic update as libcalls.
void
EmitAtomicUpdateLibcall(llvm::AtomicOrdering AO,
const llvm::function_ref<RValue(RValue)> &UpdateOp,
bool IsVolatile);
/// Emit atomic update as LLVM instructions.
void EmitAtomicUpdateOp(llvm::AtomicOrdering AO,
const llvm::function_ref<RValue(RValue)> &UpdateOp,
bool IsVolatile);
/// Emit atomic update as libcalls.
void EmitAtomicUpdateLibcall(llvm::AtomicOrdering AO, RValue UpdateRVal,
bool IsVolatile);
/// Emit atomic update as LLVM instructions.
void EmitAtomicUpdateOp(llvm::AtomicOrdering AO, RValue UpdateRal,
bool IsVolatile);
};
} // namespace
// This function emits any expression (scalar, complex, or aggregate)
// into a temporary alloca.
static Address buildValToTemp(CIRGenFunction &CGF, Expr *E) {
Address DeclPtr = CGF.CreateMemTemp(
E->getType(), CGF.getLoc(E->getSourceRange()), ".atomictmp");
CGF.buildAnyExprToMem(E, DeclPtr, E->getType().getQualifiers(),
/*Init*/ true);
return DeclPtr;
}
/// Does a store of the given IR type modify the full expected width?
static bool isFullSizeType(CIRGenModule &CGM, mlir::Type ty,
uint64_t expectedSize) {
return (CGM.getDataLayout().getTypeStoreSize(ty) * 8 == expectedSize);
}
/// Does the atomic type require memsetting to zero before initialization?
///
/// The IR type is provided as a way of making certain queries faster.
bool AtomicInfo::requiresMemSetZero(mlir::Type ty) const {
// If the atomic type has size padding, we definitely need a memset.
if (hasPadding())
return true;
// Otherwise, do some simple heuristics to try to avoid it:
switch (getEvaluationKind()) {
// For scalars and complexes, check whether the store size of the
// type uses the full size.
case TEK_Scalar:
return !isFullSizeType(CGF.CGM, ty, AtomicSizeInBits);
case TEK_Complex:
llvm_unreachable("NYI");
// Padding in structs has an undefined bit pattern. User beware.
case TEK_Aggregate:
return false;
}
llvm_unreachable("bad evaluation kind");
}
Address AtomicInfo::castToAtomicIntPointer(Address addr) const {
auto intTy = mlir::dyn_cast<mlir::cir::IntType>(addr.getElementType());
// Don't bother with int casts if the integer size is the same.
if (intTy && intTy.getWidth() == AtomicSizeInBits)
return addr;
auto ty = CGF.getBuilder().getUIntNTy(AtomicSizeInBits);
return addr.withElementType(ty);
}
Address AtomicInfo::convertToAtomicIntPointer(Address Addr) const {
auto Ty = Addr.getElementType();
uint64_t SourceSizeInBits = CGF.CGM.getDataLayout().getTypeSizeInBits(Ty);
if (SourceSizeInBits != AtomicSizeInBits) {
llvm_unreachable("NYI");
}
return castToAtomicIntPointer(Addr);
}
Address AtomicInfo::CreateTempAlloca() const {
Address TempAlloca = CGF.CreateMemTemp(
(LVal.isBitField() && ValueSizeInBits > AtomicSizeInBits) ? ValueTy
: AtomicTy,
getAtomicAlignment(), loc, "atomic-temp");
// Cast to pointer to value type for bitfields.
if (LVal.isBitField()) {
llvm_unreachable("NYI");
}
return TempAlloca;
}
// If the value comes from a ConstOp + IntAttr, retrieve and skip a series
// of casts if necessary.
//
// FIXME(cir): figure out warning issue and move this to CIRBaseBuilder.h
static mlir::cir::IntAttr getConstOpIntAttr(mlir::Value v) {
mlir::Operation *op = v.getDefiningOp();
mlir::cir::IntAttr constVal;
while (auto c = dyn_cast<mlir::cir::CastOp>(op))
op = c.getOperand().getDefiningOp();
if (auto c = dyn_cast<mlir::cir::ConstantOp>(op)) {
if (mlir::isa<mlir::cir::IntType>(c.getType()))
constVal = mlir::cast<mlir::cir::IntAttr>(c.getValue());
}
return constVal;
}
// Inspect a value that is the strong/weak flag for a compare-exchange. If it
// is a constant of intergral or boolean type, set `val` to the constant's
// boolean value and return true. Otherwise leave `val` unchanged and return
// false.
static bool isCstWeak(mlir::Value weakVal, bool &val) {
mlir::Operation *op = weakVal.getDefiningOp();
while (auto c = dyn_cast<mlir::cir::CastOp>(op)) {
op = c.getOperand().getDefiningOp();
}
if (auto c = dyn_cast<mlir::cir::ConstantOp>(op)) {
if (mlir::isa<mlir::cir::IntType>(c.getType())) {
val = mlir::cast<mlir::cir::IntAttr>(c.getValue()).getUInt() != 0;
return true;
} else if (mlir::isa<mlir::cir::BoolType>(c.getType())) {
val = mlir::cast<mlir::cir::BoolAttr>(c.getValue()).getValue();
return true;
}
}
return false;
}
// Functions that help with the creation of compiler-generated switch
// statements that are used to implement non-constant memory order parameters.
// Create a new region. Create a block within the region. Add a "break"
// statement to the block. Set the builder's insertion point to before the
// "break" statement. Add the new region to the given container.
template <typename RegionsCont>
static void startRegion(mlir::OpBuilder &builder, RegionsCont &Regions,
mlir::Location loc) {
Regions.push_back(std::make_unique<mlir::Region>());
mlir::Region *Region = Regions.back().get();
mlir::Block *Block = builder.createBlock(Region);
builder.setInsertionPointToEnd(Block);
auto Break = builder.create<mlir::cir::BreakOp>(loc);
builder.setInsertionPoint(Break);
}
// Create a "default:" label and add it to the given collection of case labels.
// Create the region that will hold the body of the "default:" block.
template <typename CaseAttrsCont, typename RegionsCont>
static void buildDefaultCase(mlir::OpBuilder &builder, CaseAttrsCont &CaseAttrs,
RegionsCont &Regions, mlir::Location loc) {
auto Context = builder.getContext();
auto EmptyArrayAttr = builder.getArrayAttr({});
auto DefaultKind =
mlir::cir::CaseOpKindAttr::get(Context, mlir::cir::CaseOpKind::Default);
auto DefaultAttr =
mlir::cir::CaseAttr::get(Context, EmptyArrayAttr, DefaultKind);
CaseAttrs.push_back(DefaultAttr);
startRegion(builder, Regions, loc);
}
// Create a single "case" label with the given MemOrder as its value. Add the
// "case" label to the given collection of case labels. Create the region that
// will hold the body of the "case" block.
template <typename CaseAttrsCont, typename RegionsCont>
static void
buildSingleMemOrderCase(mlir::OpBuilder &builder, CaseAttrsCont &CaseAttrs,
RegionsCont &Regions, mlir::Location loc,
mlir::Type Type, mlir::cir::MemOrder Order) {
auto Context = builder.getContext();
SmallVector<mlir::Attribute, 1> OneOrder{
mlir::cir::IntAttr::get(Type, static_cast<int>(Order))};
auto OneAttribute = builder.getArrayAttr(OneOrder);
auto CaseKind =
mlir::cir::CaseOpKindAttr::get(Context, mlir::cir::CaseOpKind::Equal);
auto CaseAttr = mlir::cir::CaseAttr::get(Context, OneAttribute, CaseKind);
CaseAttrs.push_back(CaseAttr);
startRegion(builder, Regions, loc);
}
// Create a pair of "case" labels with the given MemOrders as their values.
// Add the combined "case" attribute to the given collection of case labels.
// Create the region that will hold the body of the "case" block.
template <typename CaseAttrsCont, typename RegionsCont>
static void buildDoubleMemOrderCase(mlir::OpBuilder &builder,
CaseAttrsCont &CaseAttrs,
RegionsCont &Regions, mlir::Location loc,
mlir::Type Type, mlir::cir::MemOrder Order1,
mlir::cir::MemOrder Order2) {
auto Context = builder.getContext();
SmallVector<mlir::Attribute, 2> TwoOrders{
mlir::cir::IntAttr::get(Type, static_cast<int>(Order1)),
mlir::cir::IntAttr::get(Type, static_cast<int>(Order2))};
auto TwoAttributes = builder.getArrayAttr(TwoOrders);
auto CaseKind =
mlir::cir::CaseOpKindAttr::get(Context, mlir::cir::CaseOpKind::Anyof);
auto CaseAttr = mlir::cir::CaseAttr::get(Context, TwoAttributes, CaseKind);
CaseAttrs.push_back(CaseAttr);
startRegion(builder, Regions, loc);
}
static void buildAtomicCmpXchg(CIRGenFunction &CGF, AtomicExpr *E, bool IsWeak,
Address Dest, Address Ptr, Address Val1,
Address Val2, uint64_t Size,
mlir::cir::MemOrder SuccessOrder,
mlir::cir::MemOrder FailureOrder,
llvm::SyncScope::ID Scope) {
auto &builder = CGF.getBuilder();
auto loc = CGF.getLoc(E->getSourceRange());
auto Expected = builder.createLoad(loc, Val1);
auto Desired = builder.createLoad(loc, Val2);
auto boolTy = builder.getBoolTy();
auto cmpxchg = builder.create<mlir::cir::AtomicCmpXchg>(
loc, Expected.getType(), boolTy, Ptr.getPointer(), Expected, Desired,
SuccessOrder, FailureOrder);
cmpxchg.setIsVolatile(E->isVolatile());
cmpxchg.setWeak(IsWeak);
auto cmp = builder.createNot(cmpxchg.getCmp());
builder.create<mlir::cir::IfOp>(
loc, cmp, false, [&](mlir::OpBuilder &, mlir::Location) {
auto ptrTy =
mlir::cast<mlir::cir::PointerType>(Val1.getPointer().getType());
if (Val1.getElementType() != ptrTy.getPointee()) {
Val1 = Val1.withPointer(builder.createPtrBitcast(
Val1.getPointer(), Val1.getElementType()));
}
builder.createStore(loc, cmpxchg.getOld(), Val1);
builder.createYield(loc);
});
// Update the memory at Dest with Cmp's value.
CGF.buildStoreOfScalar(cmpxchg.getCmp(),
CGF.makeAddrLValue(Dest, E->getType()));
}
/// Given an ordering required on success, emit all possible cmpxchg
/// instructions to cope with the provided (but possibly only dynamically known)
/// FailureOrder.
static void buildAtomicCmpXchgFailureSet(
CIRGenFunction &CGF, AtomicExpr *E, bool IsWeak, Address Dest, Address Ptr,
Address Val1, Address Val2, mlir::Value FailureOrderVal, uint64_t Size,
mlir::cir::MemOrder SuccessOrder, llvm::SyncScope::ID Scope) {
mlir::cir::MemOrder FailureOrder;
if (auto ordAttr = getConstOpIntAttr(FailureOrderVal)) {
// We should not ever get to a case where the ordering isn't a valid CABI
// value, but it's hard to enforce that in general.
auto ord = ordAttr.getUInt();
if (!mlir::cir::isValidCIRAtomicOrderingCABI(ord)) {
FailureOrder = mlir::cir::MemOrder::Relaxed;
} else {
switch ((mlir::cir::MemOrder)ord) {
case mlir::cir::MemOrder::Relaxed:
// 31.7.2.18: "The failure argument shall not be memory_order_release
// nor memory_order_acq_rel". Fallback to monotonic.
case mlir::cir::MemOrder::Release:
case mlir::cir::MemOrder::AcquireRelease:
FailureOrder = mlir::cir::MemOrder::Relaxed;
break;
case mlir::cir::MemOrder::Consume:
case mlir::cir::MemOrder::Acquire:
FailureOrder = mlir::cir::MemOrder::Acquire;
break;
case mlir::cir::MemOrder::SequentiallyConsistent:
FailureOrder = mlir::cir::MemOrder::SequentiallyConsistent;
break;
}
}
// Prior to c++17, "the failure argument shall be no stronger than the
// success argument". This condition has been lifted and the only
// precondition is 31.7.2.18. Effectively treat this as a DR and skip
// language version checks.
buildAtomicCmpXchg(CGF, E, IsWeak, Dest, Ptr, Val1, Val2, Size,
SuccessOrder, FailureOrder, Scope);
return;
}
// The failure memory order is not a compile-time value. The CIR atomic ops
// can't handle a runtime value; all memory orders must be hard coded.
// Generate a "switch" statement that converts the runtime value into a
// compile-time value.
CGF.getBuilder().create<mlir::cir::SwitchOp>(
FailureOrderVal.getLoc(), FailureOrderVal,
[&](mlir::OpBuilder &builder, mlir::Location loc,
mlir::OperationState &os) {
SmallVector<mlir::Attribute, 3> CaseAttrs;
SmallVector<std::unique_ptr<mlir::Region>, 3> Regions;
// default:
// Unsupported memory orders get generated as memory_order_relaxed,
// because there is no practical way to report an error at runtime.
buildDefaultCase(builder, CaseAttrs, Regions, loc);
buildAtomicCmpXchg(CGF, E, IsWeak, Dest, Ptr, Val1, Val2, Size,
SuccessOrder, mlir::cir::MemOrder::Relaxed, Scope);
// case consume:
// case acquire:
// memory_order_consume is not implemented and always falls back to
// memory_order_acquire
buildDoubleMemOrderCase(
builder, CaseAttrs, Regions, loc, FailureOrderVal.getType(),
mlir::cir::MemOrder::Consume, mlir::cir::MemOrder::Acquire);
buildAtomicCmpXchg(CGF, E, IsWeak, Dest, Ptr, Val1, Val2, Size,
SuccessOrder, mlir::cir::MemOrder::Acquire, Scope);
// A failed compare-exchange is a read-only operation. So
// memory_order_release and memory_order_acq_rel are not supported for
// the failure memory order. They fall back to memory_order_relaxed.
// case seq_cst:
buildSingleMemOrderCase(builder, CaseAttrs, Regions, loc,
FailureOrderVal.getType(),
mlir::cir::MemOrder::SequentiallyConsistent);
buildAtomicCmpXchg(CGF, E, IsWeak, Dest, Ptr, Val1, Val2, Size,
SuccessOrder,
mlir::cir::MemOrder::SequentiallyConsistent, Scope);
os.addRegions(Regions);
os.addAttribute("cases", builder.getArrayAttr(CaseAttrs));
});
}
static void buildAtomicOp(CIRGenFunction &CGF, AtomicExpr *E, Address Dest,
Address Ptr, Address Val1, Address Val2,
mlir::Value IsWeak, mlir::Value FailureOrder,
uint64_t Size, mlir::cir::MemOrder Order,
uint8_t Scope) {
assert(!MissingFeatures::syncScopeID());
StringRef Op;
auto &builder = CGF.getBuilder();
auto loc = CGF.getLoc(E->getSourceRange());
auto orderAttr = mlir::cir::MemOrderAttr::get(builder.getContext(), Order);
mlir::cir::AtomicFetchKindAttr fetchAttr;
bool fetchFirst = true;
switch (E->getOp()) {
case AtomicExpr::AO__c11_atomic_init:
case AtomicExpr::AO__opencl_atomic_init:
llvm_unreachable("Already handled!");
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
case AtomicExpr::AO__hip_atomic_compare_exchange_strong:
case AtomicExpr::AO__opencl_atomic_compare_exchange_strong:
buildAtomicCmpXchgFailureSet(CGF, E, false, Dest, Ptr, Val1, Val2,
FailureOrder, Size, Order, Scope);
return;
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
case AtomicExpr::AO__opencl_atomic_compare_exchange_weak:
case AtomicExpr::AO__hip_atomic_compare_exchange_weak:
llvm_unreachable("NYI");
return;
case AtomicExpr::AO__atomic_compare_exchange:
case AtomicExpr::AO__atomic_compare_exchange_n:
case AtomicExpr::AO__scoped_atomic_compare_exchange:
case AtomicExpr::AO__scoped_atomic_compare_exchange_n: {
bool weakVal;
if (isCstWeak(IsWeak, weakVal)) {
buildAtomicCmpXchgFailureSet(CGF, E, weakVal, Dest, Ptr, Val1, Val2,
FailureOrder, Size, Order, Scope);
} else {
llvm_unreachable("NYI");
}
return;
}
case AtomicExpr::AO__c11_atomic_load:
case AtomicExpr::AO__opencl_atomic_load:
case AtomicExpr::AO__hip_atomic_load:
case AtomicExpr::AO__atomic_load_n:
case AtomicExpr::AO__atomic_load:
case AtomicExpr::AO__scoped_atomic_load_n:
case AtomicExpr::AO__scoped_atomic_load: {
auto *load = builder.createLoad(loc, Ptr).getDefiningOp();
// FIXME(cir): add scope information.
assert(!MissingFeatures::syncScopeID());
load->setAttr("mem_order", orderAttr);
if (E->isVolatile())
load->setAttr("is_volatile", mlir::UnitAttr::get(builder.getContext()));
// TODO(cir): this logic should be part of createStore, but doing so
// currently breaks CodeGen/union.cpp and CodeGen/union.cpp.
auto ptrTy =
mlir::cast<mlir::cir::PointerType>(Dest.getPointer().getType());
if (Dest.getElementType() != ptrTy.getPointee()) {
Dest = Dest.withPointer(
builder.createPtrBitcast(Dest.getPointer(), Dest.getElementType()));
}
builder.createStore(loc, load->getResult(0), Dest);
return;
}
case AtomicExpr::AO__c11_atomic_store:
case AtomicExpr::AO__opencl_atomic_store:
case AtomicExpr::AO__hip_atomic_store:
case AtomicExpr::AO__atomic_store:
case AtomicExpr::AO__atomic_store_n:
case AtomicExpr::AO__scoped_atomic_store:
case AtomicExpr::AO__scoped_atomic_store_n: {
auto loadVal1 = builder.createLoad(loc, Val1);
// FIXME(cir): add scope information.
assert(!MissingFeatures::syncScopeID());
builder.createStore(loc, loadVal1, Ptr, E->isVolatile(),
/*alignment=*/mlir::IntegerAttr{}, orderAttr);
return;
}
case AtomicExpr::AO__c11_atomic_exchange:
case AtomicExpr::AO__hip_atomic_exchange:
case AtomicExpr::AO__opencl_atomic_exchange:
case AtomicExpr::AO__atomic_exchange_n:
case AtomicExpr::AO__atomic_exchange:
case AtomicExpr::AO__scoped_atomic_exchange_n:
case AtomicExpr::AO__scoped_atomic_exchange:
Op = mlir::cir::AtomicXchg::getOperationName();
break;
case AtomicExpr::AO__atomic_add_fetch:
case AtomicExpr::AO__scoped_atomic_add_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_add:
case AtomicExpr::AO__hip_atomic_fetch_add:
case AtomicExpr::AO__opencl_atomic_fetch_add:
case AtomicExpr::AO__atomic_fetch_add:
case AtomicExpr::AO__scoped_atomic_fetch_add:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Add);
break;
case AtomicExpr::AO__atomic_sub_fetch:
case AtomicExpr::AO__scoped_atomic_sub_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_sub:
case AtomicExpr::AO__hip_atomic_fetch_sub:
case AtomicExpr::AO__opencl_atomic_fetch_sub:
case AtomicExpr::AO__atomic_fetch_sub:
case AtomicExpr::AO__scoped_atomic_fetch_sub:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Sub);
break;
case AtomicExpr::AO__atomic_min_fetch:
case AtomicExpr::AO__scoped_atomic_min_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_min:
case AtomicExpr::AO__hip_atomic_fetch_min:
case AtomicExpr::AO__opencl_atomic_fetch_min:
case AtomicExpr::AO__atomic_fetch_min:
case AtomicExpr::AO__scoped_atomic_fetch_min:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Min);
break;
case AtomicExpr::AO__atomic_max_fetch:
case AtomicExpr::AO__scoped_atomic_max_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_max:
case AtomicExpr::AO__hip_atomic_fetch_max:
case AtomicExpr::AO__opencl_atomic_fetch_max:
case AtomicExpr::AO__atomic_fetch_max:
case AtomicExpr::AO__scoped_atomic_fetch_max:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Max);
break;
case AtomicExpr::AO__atomic_and_fetch:
case AtomicExpr::AO__scoped_atomic_and_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_and:
case AtomicExpr::AO__hip_atomic_fetch_and:
case AtomicExpr::AO__opencl_atomic_fetch_and:
case AtomicExpr::AO__atomic_fetch_and:
case AtomicExpr::AO__scoped_atomic_fetch_and:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::And);
break;
case AtomicExpr::AO__atomic_or_fetch:
case AtomicExpr::AO__scoped_atomic_or_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_or:
case AtomicExpr::AO__hip_atomic_fetch_or:
case AtomicExpr::AO__opencl_atomic_fetch_or:
case AtomicExpr::AO__atomic_fetch_or:
case AtomicExpr::AO__scoped_atomic_fetch_or:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Or);
break;
case AtomicExpr::AO__atomic_xor_fetch:
case AtomicExpr::AO__scoped_atomic_xor_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_xor:
case AtomicExpr::AO__hip_atomic_fetch_xor:
case AtomicExpr::AO__opencl_atomic_fetch_xor:
case AtomicExpr::AO__atomic_fetch_xor:
case AtomicExpr::AO__scoped_atomic_fetch_xor:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Xor);
break;
case AtomicExpr::AO__atomic_nand_fetch:
case AtomicExpr::AO__scoped_atomic_nand_fetch:
fetchFirst = false;
[[fallthrough]];
case AtomicExpr::AO__c11_atomic_fetch_nand:
case AtomicExpr::AO__atomic_fetch_nand:
case AtomicExpr::AO__scoped_atomic_fetch_nand:
Op = mlir::cir::AtomicFetch::getOperationName();
fetchAttr = mlir::cir::AtomicFetchKindAttr::get(
builder.getContext(), mlir::cir::AtomicFetchKind::Nand);
break;
}
assert(Op.size() && "expected operation name to build");
auto LoadVal1 = builder.createLoad(loc, Val1);
SmallVector<mlir::Value> atomicOperands = {Ptr.getPointer(), LoadVal1};
SmallVector<mlir::Type> atomicResTys = {LoadVal1.getType()};
auto RMWI = builder.create(loc, builder.getStringAttr(Op), atomicOperands,
atomicResTys, {});
if (fetchAttr)
RMWI->setAttr("binop", fetchAttr);
RMWI->setAttr("mem_order", orderAttr);
if (E->isVolatile())
RMWI->setAttr("is_volatile", mlir::UnitAttr::get(builder.getContext()));
if (fetchFirst && Op == mlir::cir::AtomicFetch::getOperationName())
RMWI->setAttr("fetch_first", mlir::UnitAttr::get(builder.getContext()));
auto Result = RMWI->getResult(0);
// TODO(cir): this logic should be part of createStore, but doing so currently
// breaks CodeGen/union.cpp and CodeGen/union.cpp.
auto ptrTy = mlir::cast<mlir::cir::PointerType>(Dest.getPointer().getType());
if (Dest.getElementType() != ptrTy.getPointee()) {
Dest = Dest.withPointer(
builder.createPtrBitcast(Dest.getPointer(), Dest.getElementType()));
}
builder.createStore(loc, Result, Dest);
}
static RValue buildAtomicLibcall(CIRGenFunction &CGF, StringRef fnName,
QualType resultType, CallArgList &args) {
[[maybe_unused]] const CIRGenFunctionInfo &fnInfo =
CGF.CGM.getTypes().arrangeBuiltinFunctionCall(resultType, args);
[[maybe_unused]] auto fnTy = CGF.CGM.getTypes().GetFunctionType(fnInfo);
llvm_unreachable("NYI");
}
static void buildAtomicOp(CIRGenFunction &CGF, AtomicExpr *Expr, Address Dest,
Address Ptr, Address Val1, Address Val2,
mlir::Value IsWeak, mlir::Value FailureOrder,
uint64_t Size, mlir::cir::MemOrder Order,
mlir::Value Scope) {
auto ScopeModel = Expr->getScopeModel();
// LLVM atomic instructions always have synch scope. If clang atomic
// expression has no scope operand, use default LLVM synch scope.
if (!ScopeModel) {
assert(!MissingFeatures::syncScopeID());
buildAtomicOp(CGF, Expr, Dest, Ptr, Val1, Val2, IsWeak, FailureOrder, Size,
Order, /*FIXME(cir): LLVM default scope*/ 1);
return;
}
// Handle constant scope.
if (getConstOpIntAttr(Scope)) {
assert(!MissingFeatures::syncScopeID());
llvm_unreachable("NYI");
return;
}
// Handle non-constant scope.
llvm_unreachable("NYI");
}
RValue CIRGenFunction::buildAtomicExpr(AtomicExpr *E) {
QualType AtomicTy = E->getPtr()->getType()->getPointeeType();
QualType MemTy = AtomicTy;
if (const AtomicType *AT = AtomicTy->getAs<AtomicType>())
MemTy = AT->getValueType();
mlir::Value IsWeak = nullptr, OrderFail = nullptr;
Address Val1 = Address::invalid();
Address Val2 = Address::invalid();
Address Dest = Address::invalid();
Address Ptr = buildPointerWithAlignment(E->getPtr());
if (E->getOp() == AtomicExpr::AO__c11_atomic_init ||
E->getOp() == AtomicExpr::AO__opencl_atomic_init) {
LValue lvalue = makeAddrLValue(Ptr, AtomicTy);
buildAtomicInit(E->getVal1(), lvalue);
return RValue::get(nullptr);
}
auto TInfo = getContext().getTypeInfoInChars(AtomicTy);
uint64_t Size = TInfo.Width.getQuantity();
unsigned MaxInlineWidthInBits = getTarget().getMaxAtomicInlineWidth();
CharUnits MaxInlineWidth =
getContext().toCharUnitsFromBits(MaxInlineWidthInBits);
DiagnosticsEngine &Diags = CGM.getDiags();
bool Misaligned = (Ptr.getAlignment() % TInfo.Width) != 0;
bool Oversized = getContext().toBits(TInfo.Width) > MaxInlineWidthInBits;
if (Misaligned) {
Diags.Report(E->getBeginLoc(), diag::warn_atomic_op_misaligned)
<< (int)TInfo.Width.getQuantity()
<< (int)Ptr.getAlignment().getQuantity();
}
if (Oversized) {
Diags.Report(E->getBeginLoc(), diag::warn_atomic_op_oversized)
<< (int)TInfo.Width.getQuantity() << (int)MaxInlineWidth.getQuantity();
}
auto Order = buildScalarExpr(E->getOrder());
auto Scope = E->getScopeModel() ? buildScalarExpr(E->getScope()) : nullptr;
bool ShouldCastToIntPtrTy = true;
switch (E->getOp()) {
case AtomicExpr::AO__c11_atomic_init:
case AtomicExpr::AO__opencl_atomic_init:
llvm_unreachable("Already handled above with EmitAtomicInit!");
case AtomicExpr::AO__atomic_load_n:
case AtomicExpr::AO__scoped_atomic_load_n:
case AtomicExpr::AO__c11_atomic_load:
case AtomicExpr::AO__opencl_atomic_load:
case AtomicExpr::AO__hip_atomic_load:
break;
case AtomicExpr::AO__atomic_load:
case AtomicExpr::AO__scoped_atomic_load:
Dest = buildPointerWithAlignment(E->getVal1());
break;
case AtomicExpr::AO__atomic_store:
case AtomicExpr::AO__scoped_atomic_store:
Val1 = buildPointerWithAlignment(E->getVal1());
break;
case AtomicExpr::AO__atomic_exchange:
case AtomicExpr::AO__scoped_atomic_exchange:
Val1 = buildPointerWithAlignment(E->getVal1());
Dest = buildPointerWithAlignment(E->getVal2());
break;
case AtomicExpr::AO__atomic_compare_exchange:
case AtomicExpr::AO__atomic_compare_exchange_n:
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
case AtomicExpr::AO__hip_atomic_compare_exchange_weak:
case AtomicExpr::AO__hip_atomic_compare_exchange_strong:
case AtomicExpr::AO__opencl_atomic_compare_exchange_weak:
case AtomicExpr::AO__opencl_atomic_compare_exchange_strong:
case AtomicExpr::AO__scoped_atomic_compare_exchange:
case AtomicExpr::AO__scoped_atomic_compare_exchange_n:
Val1 = buildPointerWithAlignment(E->getVal1());
if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange ||
E->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange)
Val2 = buildPointerWithAlignment(E->getVal2());
else
Val2 = buildValToTemp(*this, E->getVal2());
OrderFail = buildScalarExpr(E->getOrderFail());
if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n ||
E->getOp() == AtomicExpr::AO__atomic_compare_exchange ||
E->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange_n ||
E->getOp() == AtomicExpr::AO__scoped_atomic_compare_exchange) {
IsWeak = buildScalarExpr(E->getWeak());
}
break;
case AtomicExpr::AO__c11_atomic_fetch_add:
case AtomicExpr::AO__c11_atomic_fetch_sub:
case AtomicExpr::AO__hip_atomic_fetch_add:
case AtomicExpr::AO__hip_atomic_fetch_sub:
case AtomicExpr::AO__opencl_atomic_fetch_add:
case AtomicExpr::AO__opencl_atomic_fetch_sub:
if (MemTy->isPointerType()) {
llvm_unreachable("NYI");
}
[[fallthrough]];
case AtomicExpr::AO__atomic_fetch_add:
case AtomicExpr::AO__atomic_fetch_max:
case AtomicExpr::AO__atomic_fetch_min:
case AtomicExpr::AO__atomic_fetch_sub:
case AtomicExpr::AO__atomic_add_fetch:
case AtomicExpr::AO__atomic_max_fetch:
case AtomicExpr::AO__atomic_min_fetch:
case AtomicExpr::AO__atomic_sub_fetch:
case AtomicExpr::AO__c11_atomic_fetch_max:
case AtomicExpr::AO__c11_atomic_fetch_min:
case AtomicExpr::AO__opencl_atomic_fetch_max:
case AtomicExpr::AO__opencl_atomic_fetch_min:
case AtomicExpr::AO__hip_atomic_fetch_max:
case AtomicExpr::AO__hip_atomic_fetch_min:
case AtomicExpr::AO__scoped_atomic_fetch_add:
case AtomicExpr::AO__scoped_atomic_fetch_max:
case AtomicExpr::AO__scoped_atomic_fetch_min:
case AtomicExpr::AO__scoped_atomic_fetch_sub:
case AtomicExpr::AO__scoped_atomic_add_fetch:
case AtomicExpr::AO__scoped_atomic_max_fetch:
case AtomicExpr::AO__scoped_atomic_min_fetch:
case AtomicExpr::AO__scoped_atomic_sub_fetch:
ShouldCastToIntPtrTy = !MemTy->isFloatingType();
[[fallthrough]];
case AtomicExpr::AO__atomic_fetch_and:
case AtomicExpr::AO__atomic_fetch_nand:
case AtomicExpr::AO__atomic_fetch_or:
case AtomicExpr::AO__atomic_fetch_xor:
case AtomicExpr::AO__atomic_and_fetch:
case AtomicExpr::AO__atomic_nand_fetch:
case AtomicExpr::AO__atomic_or_fetch:
case AtomicExpr::AO__atomic_xor_fetch:
case AtomicExpr::AO__atomic_store_n:
case AtomicExpr::AO__atomic_exchange_n:
case AtomicExpr::AO__c11_atomic_fetch_and:
case AtomicExpr::AO__c11_atomic_fetch_nand:
case AtomicExpr::AO__c11_atomic_fetch_or:
case AtomicExpr::AO__c11_atomic_fetch_xor:
case AtomicExpr::AO__c11_atomic_store:
case AtomicExpr::AO__c11_atomic_exchange:
case AtomicExpr::AO__hip_atomic_fetch_and:
case AtomicExpr::AO__hip_atomic_fetch_or:
case AtomicExpr::AO__hip_atomic_fetch_xor:
case AtomicExpr::AO__hip_atomic_store:
case AtomicExpr::AO__hip_atomic_exchange:
case AtomicExpr::AO__opencl_atomic_fetch_and:
case AtomicExpr::AO__opencl_atomic_fetch_or:
case AtomicExpr::AO__opencl_atomic_fetch_xor:
case AtomicExpr::AO__opencl_atomic_store:
case AtomicExpr::AO__opencl_atomic_exchange:
case AtomicExpr::AO__scoped_atomic_fetch_and:
case AtomicExpr::AO__scoped_atomic_fetch_nand:
case AtomicExpr::AO__scoped_atomic_fetch_or:
case AtomicExpr::AO__scoped_atomic_fetch_xor:
case AtomicExpr::AO__scoped_atomic_and_fetch:
case AtomicExpr::AO__scoped_atomic_nand_fetch:
case AtomicExpr::AO__scoped_atomic_or_fetch:
case AtomicExpr::AO__scoped_atomic_xor_fetch:
case AtomicExpr::AO__scoped_atomic_store_n:
case AtomicExpr::AO__scoped_atomic_exchange_n:
Val1 = buildValToTemp(*this, E->getVal1());
break;
}
QualType RValTy = E->getType().getUnqualifiedType();
// The inlined atomics only function on iN types, where N is a power of 2. We
// need to make sure (via temporaries if necessary) that all incoming values
// are compatible.
LValue AtomicVal = makeAddrLValue(Ptr, AtomicTy);
AtomicInfo Atomics(*this, AtomicVal, getLoc(E->getSourceRange()));
if (ShouldCastToIntPtrTy) {
Ptr = Atomics.castToAtomicIntPointer(Ptr);
if (Val1.isValid())
Val1 = Atomics.convertToAtomicIntPointer(Val1);
if (Val2.isValid())
Val2 = Atomics.convertToAtomicIntPointer(Val2);
}
if (Dest.isValid()) {
if (ShouldCastToIntPtrTy)
Dest = Atomics.castToAtomicIntPointer(Dest);
} else if (E->isCmpXChg())
Dest = CreateMemTemp(RValTy, getLoc(E->getSourceRange()), "cmpxchg.bool");
else if (!RValTy->isVoidType()) {
Dest = Atomics.CreateTempAlloca();
if (ShouldCastToIntPtrTy)
Dest = Atomics.castToAtomicIntPointer(Dest);
}
bool PowerOf2Size = (Size & (Size - 1)) == 0;
bool UseLibcall = !PowerOf2Size || (Size > 16);
// For atomics larger than 16 bytes, emit a libcall from the frontend. This
// avoids the overhead of dealing with excessively-large value types in IR.
// Non-power-of-2 values also lower to libcall here, as they are not currently
// permitted in IR instructions (although that constraint could be relaxed in
// the future). For other cases where a libcall is required on a given
// platform, we let the backend handle it (this includes handling for all of
// the size-optimized libcall variants, which are only valid up to 16 bytes.)
//
// See: https://llvm.org/docs/Atomics.html#libcalls-atomic
if (UseLibcall) {
CallArgList Args;
// For non-optimized library calls, the size is the first parameter.
Args.add(RValue::get(builder.getConstInt(getLoc(E->getSourceRange()),
SizeTy, Size)),
getContext().getSizeType());
// The atomic address is the second parameter.
// The OpenCL atomic library functions only accept pointer arguments to
// generic address space.
auto CastToGenericAddrSpace = [&](mlir::Value V, QualType PT) {
if (!E->isOpenCL())
return V;
llvm_unreachable("NYI");
};
Args.add(RValue::get(CastToGenericAddrSpace(Ptr.emitRawPointer(),
E->getPtr()->getType())),
getContext().VoidPtrTy);
// The next 1-3 parameters are op-dependent.
std::string LibCallName;
QualType RetTy;
bool HaveRetTy = false;
switch (E->getOp()) {
case AtomicExpr::AO__c11_atomic_init:
case AtomicExpr::AO__opencl_atomic_init:
llvm_unreachable("Already handled!");
// There is only one libcall for compare an exchange, because there is no
// optimisation benefit possible from a libcall version of a weak compare
// and exchange.
// bool __atomic_compare_exchange(size_t size, void *mem, void *expected,
// void *desired, int success, int failure)
case AtomicExpr::AO__atomic_compare_exchange:
case AtomicExpr::AO__atomic_compare_exchange_n:
case AtomicExpr::AO__c11_atomic_compare_exchange_weak:
case AtomicExpr::AO__c11_atomic_compare_exchange_strong:
case AtomicExpr::AO__hip_atomic_compare_exchange_weak:
case AtomicExpr::AO__hip_atomic_compare_exchange_strong:
case AtomicExpr::AO__opencl_atomic_compare_exchange_weak:
case AtomicExpr::AO__opencl_atomic_compare_exchange_strong:
case AtomicExpr::AO__scoped_atomic_compare_exchange:
case AtomicExpr::AO__scoped_atomic_compare_exchange_n:
LibCallName = "__atomic_compare_exchange";
llvm_unreachable("NYI");
break;
// void __atomic_exchange(size_t size, void *mem, void *val, void *return,
// int order)
case AtomicExpr::AO__atomic_exchange:
case AtomicExpr::AO__atomic_exchange_n:
case AtomicExpr::AO__c11_atomic_exchange:
case AtomicExpr::AO__hip_atomic_exchange:
case AtomicExpr::AO__opencl_atomic_exchange:
case AtomicExpr::AO__scoped_atomic_exchange:
case AtomicExpr::AO__scoped_atomic_exchange_n:
LibCallName = "__atomic_exchange";
llvm_unreachable("NYI");
break;
// void __atomic_store(size_t size, void *mem, void *val, int order)
case AtomicExpr::AO__atomic_store:
case AtomicExpr::AO__atomic_store_n:
case AtomicExpr::AO__c11_atomic_store:
case AtomicExpr::AO__hip_atomic_store:
case AtomicExpr::AO__opencl_atomic_store:
case AtomicExpr::AO__scoped_atomic_store:
case AtomicExpr::AO__scoped_atomic_store_n:
LibCallName = "__atomic_store";
llvm_unreachable("NYI");
break;
// void __atomic_load(size_t size, void *mem, void *return, int order)
case AtomicExpr::AO__atomic_load:
case AtomicExpr::AO__atomic_load_n:
case AtomicExpr::AO__c11_atomic_load:
case AtomicExpr::AO__hip_atomic_load:
case AtomicExpr::AO__opencl_atomic_load:
case AtomicExpr::AO__scoped_atomic_load:
case AtomicExpr::AO__scoped_atomic_load_n:
LibCallName = "__atomic_load";
break;
case AtomicExpr::AO__atomic_add_fetch:
case AtomicExpr::AO__scoped_atomic_add_fetch:
case AtomicExpr::AO__atomic_fetch_add:
case AtomicExpr::AO__c11_atomic_fetch_add:
case AtomicExpr::AO__hip_atomic_fetch_add:
case AtomicExpr::AO__opencl_atomic_fetch_add:
case AtomicExpr::AO__scoped_atomic_fetch_add:
case AtomicExpr::AO__atomic_and_fetch:
case AtomicExpr::AO__scoped_atomic_and_fetch:
case AtomicExpr::AO__atomic_fetch_and:
case AtomicExpr::AO__c11_atomic_fetch_and:
case AtomicExpr::AO__hip_atomic_fetch_and:
case AtomicExpr::AO__opencl_atomic_fetch_and:
case AtomicExpr::AO__scoped_atomic_fetch_and:
case AtomicExpr::AO__atomic_or_fetch:
case AtomicExpr::AO__scoped_atomic_or_fetch:
case AtomicExpr::AO__atomic_fetch_or:
case AtomicExpr::AO__c11_atomic_fetch_or:
case AtomicExpr::AO__hip_atomic_fetch_or:
case AtomicExpr::AO__opencl_atomic_fetch_or:
case AtomicExpr::AO__scoped_atomic_fetch_or:
case AtomicExpr::AO__atomic_sub_fetch:
case AtomicExpr::AO__scoped_atomic_sub_fetch:
case AtomicExpr::AO__atomic_fetch_sub:
case AtomicExpr::AO__c11_atomic_fetch_sub:
case AtomicExpr::AO__hip_atomic_fetch_sub:
case AtomicExpr::AO__opencl_atomic_fetch_sub:
case AtomicExpr::AO__scoped_atomic_fetch_sub:
case AtomicExpr::AO__atomic_xor_fetch:
case AtomicExpr::AO__scoped_atomic_xor_fetch:
case AtomicExpr::AO__atomic_fetch_xor:
case AtomicExpr::AO__c11_atomic_fetch_xor:
case AtomicExpr::AO__hip_atomic_fetch_xor:
case AtomicExpr::AO__opencl_atomic_fetch_xor:
case AtomicExpr::AO__scoped_atomic_fetch_xor:
case AtomicExpr::AO__atomic_nand_fetch:
case AtomicExpr::AO__atomic_fetch_nand:
case AtomicExpr::AO__c11_atomic_fetch_nand:
case AtomicExpr::AO__scoped_atomic_fetch_nand:
case AtomicExpr::AO__scoped_atomic_nand_fetch:
case AtomicExpr::AO__atomic_min_fetch:
case AtomicExpr::AO__atomic_fetch_min:
case AtomicExpr::AO__c11_atomic_fetch_min:
case AtomicExpr::AO__hip_atomic_fetch_min:
case AtomicExpr::AO__opencl_atomic_fetch_min:
case AtomicExpr::AO__scoped_atomic_fetch_min:
case AtomicExpr::AO__scoped_atomic_min_fetch:
case AtomicExpr::AO__atomic_max_fetch:
case AtomicExpr::AO__atomic_fetch_max:
case AtomicExpr::AO__c11_atomic_fetch_max:
case AtomicExpr::AO__hip_atomic_fetch_max:
case AtomicExpr::AO__opencl_atomic_fetch_max:
case AtomicExpr::AO__scoped_atomic_fetch_max:
case AtomicExpr::AO__scoped_atomic_max_fetch:
llvm_unreachable("Integral atomic operations always become atomicrmw!");
}
if (E->isOpenCL()) {
LibCallName =
std::string("__opencl") + StringRef(LibCallName).drop_front(1).str();
}
// By default, assume we return a value of the atomic type.
if (!HaveRetTy) {
llvm_unreachable("NYI");
}
// Order is always the last parameter.
Args.add(RValue::get(Order), getContext().IntTy);
if (E->isOpenCL()) {
llvm_unreachable("NYI");
}
[[maybe_unused]] RValue Res =
buildAtomicLibcall(*this, LibCallName, RetTy, Args);
// The value is returned directly from the libcall.
if (E->isCmpXChg()) {
llvm_unreachable("NYI");
}
if (RValTy->isVoidType()) {
llvm_unreachable("NYI");
}
llvm_unreachable("NYI");
}
[[maybe_unused]] bool IsStore =
E->getOp() == AtomicExpr::AO__c11_atomic_store ||
E->getOp() == AtomicExpr::AO__opencl_atomic_store ||
E->getOp() == AtomicExpr::AO__hip_atomic_store ||
E->getOp() == AtomicExpr::AO__atomic_store ||
E->getOp() == AtomicExpr::AO__atomic_store_n ||
E->getOp() == AtomicExpr::AO__scoped_atomic_store ||
E->getOp() == AtomicExpr::AO__scoped_atomic_store_n;
[[maybe_unused]] bool IsLoad =
E->getOp() == AtomicExpr::AO__c11_atomic_load ||
E->getOp() == AtomicExpr::AO__opencl_atomic_load ||
E->getOp() == AtomicExpr::AO__hip_atomic_load ||
E->getOp() == AtomicExpr::AO__atomic_load ||
E->getOp() == AtomicExpr::AO__atomic_load_n ||
E->getOp() == AtomicExpr::AO__scoped_atomic_load ||
E->getOp() == AtomicExpr::AO__scoped_atomic_load_n;
if (auto ordAttr = getConstOpIntAttr(Order)) {
// We should not ever get to a case where the ordering isn't a valid CABI
// value, but it's hard to enforce that in general.
auto ord = ordAttr.getUInt();
if (mlir::cir::isValidCIRAtomicOrderingCABI(ord)) {
switch ((mlir::cir::MemOrder)ord) {
case mlir::cir::MemOrder::Relaxed:
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::Relaxed, Scope);
break;
case mlir::cir::MemOrder::Consume:
case mlir::cir::MemOrder::Acquire:
if (IsStore)
break; // Avoid crashing on code with undefined behavior
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::Acquire, Scope);
break;
case mlir::cir::MemOrder::Release:
if (IsLoad)
break; // Avoid crashing on code with undefined behavior
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::Release, Scope);
break;
case mlir::cir::MemOrder::AcquireRelease:
if (IsLoad || IsStore)
break; // Avoid crashing on code with undefined behavior
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::AcquireRelease, Scope);
break;
case mlir::cir::MemOrder::SequentiallyConsistent:
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::SequentiallyConsistent, Scope);
break;
}
}
if (RValTy->isVoidType())
return RValue::get(nullptr);
return convertTempToRValue(Dest.withElementType(convertTypeForMem(RValTy)),
RValTy, E->getExprLoc());
}
// The memory order is not known at compile-time. The atomic operations
// can't handle runtime memory orders; the memory order must be hard coded.
// Generate a "switch" statement that converts a runtime value into a
// compile-time value.
builder.create<mlir::cir::SwitchOp>(
Order.getLoc(), Order,
[&](mlir::OpBuilder &builder, mlir::Location loc,
mlir::OperationState &os) {
llvm::SmallVector<mlir::Attribute, 6> CaseAttrs;
llvm::SmallVector<std::unique_ptr<mlir::Region>, 6> Regions;
// default:
// Use memory_order_relaxed for relaxed operations and for any memory
// order value that is not supported. There is no good way to report
// an unsupported memory order at runtime, hence the fallback to
// memory_order_relaxed.
buildDefaultCase(builder, CaseAttrs, Regions, loc);
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::Relaxed, Scope);
if (!IsStore) {
// case consume:
// case acquire:
// memory_order_consume is not implemented; it is always treated like
// memory_order_acquire. These memory orders are not valid for
// write-only operations.
buildDoubleMemOrderCase(builder, CaseAttrs, Regions, loc,
Order.getType(), mlir::cir::MemOrder::Consume,
mlir::cir::MemOrder::Acquire);
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail,
Size, mlir::cir::MemOrder::Acquire, Scope);
}
if (!IsLoad) {
// case release:
// memory_order_release is not valid for read-only operations.
buildSingleMemOrderCase(builder, CaseAttrs, Regions, loc,
Order.getType(),
mlir::cir::MemOrder::Release);
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail,
Size, mlir::cir::MemOrder::Release, Scope);
}
if (!IsLoad && !IsStore) {
// case acq_rel:
// memory_order_acq_rel is only valid for read-write operations.
buildSingleMemOrderCase(builder, CaseAttrs, Regions, loc,
Order.getType(),
mlir::cir::MemOrder::AcquireRelease);
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail,
Size, mlir::cir::MemOrder::AcquireRelease, Scope);
}
// case seq_cst:
buildSingleMemOrderCase(builder, CaseAttrs, Regions, loc,
Order.getType(),
mlir::cir::MemOrder::SequentiallyConsistent);
buildAtomicOp(*this, E, Dest, Ptr, Val1, Val2, IsWeak, OrderFail, Size,
mlir::cir::MemOrder::SequentiallyConsistent, Scope);
os.addRegions(Regions);
os.addAttribute("cases", builder.getArrayAttr(CaseAttrs));
});
if (RValTy->isVoidType())
return RValue::get(nullptr);
return convertTempToRValue(Dest.withElementType(convertTypeForMem(RValTy)),
RValTy, E->getExprLoc());
}
void CIRGenFunction::buildAtomicStore(RValue rvalue, LValue lvalue,
bool isInit) {
bool IsVolatile = lvalue.isVolatileQualified();
mlir::cir::MemOrder MO;
if (lvalue.getType()->isAtomicType()) {
MO = mlir::cir::MemOrder::SequentiallyConsistent;
} else {
MO = mlir::cir::MemOrder::Release;
IsVolatile = true;
}
return buildAtomicStore(rvalue, lvalue, MO, IsVolatile, isInit);
}
/// Return true if \param ValTy is a type that should be casted to integer
/// around the atomic memory operation. If \param CmpXchg is true, then the
/// cast of a floating point type is made as that instruction can not have
/// floating point operands. TODO: Allow compare-and-exchange and FP - see
/// comment in CIRGenAtomicExpandPass.cpp.
static bool shouldCastToInt(mlir::Type ValTy, bool CmpXchg) {
if (mlir::cir::isAnyFloatingPointType(ValTy))
return isa<mlir::cir::FP80Type>(ValTy) || CmpXchg;
return !isa<mlir::cir::IntType>(ValTy) && !isa<mlir::cir::PointerType>(ValTy);
}
mlir::Value AtomicInfo::getScalarRValValueOrNull(RValue RVal) const {
if (RVal.isScalar() && (!hasPadding() || !LVal.isSimple()))
return RVal.getScalarVal();
return nullptr;
}
/// Materialize an r-value into memory for the purposes of storing it
/// to an atomic type.
Address AtomicInfo::materializeRValue(RValue rvalue) const {
// Aggregate r-values are already in memory, and EmitAtomicStore
// requires them to be values of the atomic type.
if (rvalue.isAggregate())
return rvalue.getAggregateAddress();
// Otherwise, make a temporary and materialize into it.
LValue TempLV = CGF.makeAddrLValue(CreateTempAlloca(), getAtomicType());
AtomicInfo Atomics(CGF, TempLV, TempLV.getAddress().getPointer().getLoc());
Atomics.emitCopyIntoMemory(rvalue);
return TempLV.getAddress();
}
bool AtomicInfo::emitMemSetZeroIfNecessary() const {
assert(LVal.isSimple());
Address addr = LVal.getAddress();
if (!requiresMemSetZero(addr.getElementType()))
return false;
llvm_unreachable("NYI");
}
/// Copy an r-value into memory as part of storing to an atomic type.
/// This needs to create a bit-pattern suitable for atomic operations.
void AtomicInfo::emitCopyIntoMemory(RValue rvalue) const {
assert(LVal.isSimple());
// If we have an r-value, the rvalue should be of the atomic type,
// which means that the caller is responsible for having zeroed
// any padding. Just do an aggregate copy of that type.
if (rvalue.isAggregate()) {
llvm_unreachable("NYI");
return;
}
// Okay, otherwise we're copying stuff.
// Zero out the buffer if necessary.
emitMemSetZeroIfNecessary();
// Drill past the padding if present.
LValue TempLVal = projectValue();
// Okay, store the rvalue in.
if (rvalue.isScalar()) {
CGF.buildStoreOfScalar(rvalue.getScalarVal(), TempLVal, /*init*/ true);
} else {
llvm_unreachable("NYI");
}
}
mlir::Value AtomicInfo::convertRValueToInt(RValue RVal, bool CmpXchg) const {
// If we've got a scalar value of the right size, try to avoid going
// through memory. Floats get casted if needed by AtomicExpandPass.
if (auto Value = getScalarRValValueOrNull(RVal)) {
if (!shouldCastToInt(Value.getType(), CmpXchg)) {
return CGF.buildToMemory(Value, ValueTy);
} else {
llvm_unreachable("NYI");
}
}
llvm_unreachable("NYI");
}
/// Emit a store to an l-value of atomic type.
///
/// Note that the r-value is expected to be an r-value *of the atomic
/// type*; this means that for aggregate r-values, it should include
/// storage for any padding that was necessary.
void CIRGenFunction::buildAtomicStore(RValue rvalue, LValue dest,
mlir::cir::MemOrder MO, bool IsVolatile,
bool isInit) {
// If this is an aggregate r-value, it should agree in type except
// maybe for address-space qualification.
auto loc = dest.getPointer().getLoc();
assert(!rvalue.isAggregate() ||
rvalue.getAggregateAddress().getElementType() ==
dest.getAddress().getElementType());
AtomicInfo atomics(*this, dest, loc);
LValue LVal = atomics.getAtomicLValue();
// If this is an initialization, just put the value there normally.
if (LVal.isSimple()) {
if (isInit) {
atomics.emitCopyIntoMemory(rvalue);
return;
}
// Check whether we should use a library call.
if (atomics.shouldUseLibcall()) {
llvm_unreachable("NYI");
}
// Okay, we're doing this natively.
auto ValToStore = atomics.convertRValueToInt(rvalue);
// Do the atomic store.
Address Addr = atomics.getAtomicAddress();
if (auto Value = atomics.getScalarRValValueOrNull(rvalue))
if (shouldCastToInt(Value.getType(), /*CmpXchg=*/false)) {
Addr = atomics.castToAtomicIntPointer(Addr);
ValToStore = builder.createIntCast(ValToStore, Addr.getElementType());
}
auto store = builder.createStore(loc, ValToStore, Addr);
if (MO == mlir::cir::MemOrder::Acquire)
MO = mlir::cir::MemOrder::Relaxed; // Monotonic
else if (MO == mlir::cir::MemOrder::AcquireRelease)
MO = mlir::cir::MemOrder::Release;
// Initializations don't need to be atomic.
if (!isInit)
store.setMemOrder(MO);
// Other decoration.
if (IsVolatile)
store.setIsVolatile(true);
// DecorateInstructionWithTBAA
assert(!MissingFeatures::tbaa());
return;
}
llvm_unreachable("NYI");
}
void CIRGenFunction::buildAtomicInit(Expr *init, LValue dest) {
AtomicInfo atomics(*this, dest, getLoc(init->getSourceRange()));
switch (atomics.getEvaluationKind()) {
case TEK_Scalar: {
mlir::Value value = buildScalarExpr(init);
atomics.emitCopyIntoMemory(RValue::get(value));
return;
}
case TEK_Complex: {
llvm_unreachable("NYI");
return;
}
case TEK_Aggregate: {
// Fix up the destination if the initializer isn't an expression
// of atomic type.
llvm_unreachable("NYI");
return;
}
}
llvm_unreachable("bad evaluation kind");
}