https://github.com/hanickadot created https://github.com/llvm/llvm-project/pull/98756
This implements clang support for P3309 constexpr std::atomic & std::atomic_ref (currently in LWG) by allowing constant evaluation of clang's __c11_atomic_OP and GCC's __atomic_OP builtins. From e42b4a8877fed0096e44961235a61192ecb8e620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hana=20Dusi=CC=81kova=CC=81?= <hani...@hanicka.net> Date: Sat, 13 Jul 2024 20:10:26 +0200 Subject: [PATCH] [clang] constexpr atomic builtins (__c11_atomic_OP and __atomic_OP) --- clang/include/clang/Basic/Builtins.td | 84 +-- clang/lib/AST/ExprConstant.cpp | 553 +++++++++++++++++- .../SemaCXX/atomic-constexpr-c11-builtins.cpp | 288 +++++++++ .../SemaCXX/atomic-constexpr-gcc-builtins.cpp | 494 ++++++++++++++++ 4 files changed, 1371 insertions(+), 48 deletions(-) create mode 100644 clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp create mode 100644 clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index f5b15cf90d1f8..0716cf02f5110 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1682,97 +1682,97 @@ def SyncSwapN : Builtin, SyncBuiltinsTemplate { // C11 _Atomic operations for <stdatomic.h>. def C11AtomicInit : AtomicBuiltin { let Spellings = ["__c11_atomic_init"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicLoad : AtomicBuiltin { let Spellings = ["__c11_atomic_load"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicStore : AtomicBuiltin { let Spellings = ["__c11_atomic_store"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicExchange : AtomicBuiltin { let Spellings = ["__c11_atomic_exchange"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicCompareExchangeStrong : AtomicBuiltin { let Spellings = ["__c11_atomic_compare_exchange_strong"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicCompareExchangeWeak : AtomicBuiltin { let Spellings = ["__c11_atomic_compare_exchange_weak"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchAdd : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_add"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchSub : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_sub"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchAnd : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_and"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchOr : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_or"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchXor : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_xor"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchNand : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_nand"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchMax : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_max"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicFetchMin : AtomicBuiltin { let Spellings = ["__c11_atomic_fetch_min"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def C11AtomicThreadFence : Builtin { let Spellings = ["__c11_atomic_thread_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } def C11AtomicSignalFence : Builtin { let Spellings = ["__c11_atomic_signal_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } @@ -1785,157 +1785,157 @@ def C11AtomicIsLockFree : Builtin { // GNU atomic builtins. def AtomicLoad : AtomicBuiltin { let Spellings = ["__atomic_load"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicLoadN : AtomicBuiltin { let Spellings = ["__atomic_load_n"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicStore : AtomicBuiltin { let Spellings = ["__atomic_store"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicStoreN : AtomicBuiltin { let Spellings = ["__atomic_store_n"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicExchange : AtomicBuiltin { let Spellings = ["__atomic_exchange"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicExchangeN : AtomicBuiltin { let Spellings = ["__atomic_exchange_n"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicCompareExchange : AtomicBuiltin { let Spellings = ["__atomic_compare_exchange"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicCompareExchangeN : AtomicBuiltin { let Spellings = ["__atomic_compare_exchange_n"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchAdd : AtomicBuiltin { let Spellings = ["__atomic_fetch_add"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchSub : AtomicBuiltin { let Spellings = ["__atomic_fetch_sub"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchAnd : AtomicBuiltin { let Spellings = ["__atomic_fetch_and"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchOr : AtomicBuiltin { let Spellings = ["__atomic_fetch_or"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchXor : AtomicBuiltin { let Spellings = ["__atomic_fetch_xor"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicFetchNand : AtomicBuiltin { let Spellings = ["__atomic_fetch_nand"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicAddFetch : AtomicBuiltin { let Spellings = ["__atomic_add_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicSubFetch : AtomicBuiltin { let Spellings = ["__atomic_sub_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicAndFetch : AtomicBuiltin { let Spellings = ["__atomic_and_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicOrFetch : AtomicBuiltin { let Spellings = ["__atomic_or_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicXorFetch : AtomicBuiltin { let Spellings = ["__atomic_xor_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicMaxFetch : AtomicBuiltin { let Spellings = ["__atomic_max_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicMinFetch : AtomicBuiltin { let Spellings = ["__atomic_min_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicNandFetch : AtomicBuiltin { let Spellings = ["__atomic_nand_fetch"]; - let Attributes = [CustomTypeChecking]; + let Attributes = [CustomTypeChecking, Constexpr]; let Prototype = "void(...)"; } def AtomicTestAndSet : Builtin { let Spellings = ["__atomic_test_and_set"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "bool(void volatile*, int)"; } def AtomicClear : Builtin { let Spellings = ["__atomic_clear"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(void volatile*, int)"; } def AtomicThreadFence : Builtin { let Spellings = ["__atomic_thread_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } def AtomicSignalFence : Builtin { let Spellings = ["__atomic_signal_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 0aeac9d03eed3..c472b4b998aa3 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1900,6 +1900,17 @@ static bool EvaluateFixedPoint(const Expr *E, APFixedPoint &Result, // Misc utilities //===----------------------------------------------------------------------===// +static bool isOnePastTheEndOfCompleteObject(const ASTContext &Ctx, + const LValue &LV); + +enum class SizeOfType { + SizeOf, + DataSizeOf, +}; + +static bool HandleSizeof(EvalInfo &Info, SourceLocation Loc, QualType Type, + CharUnits &Size, SizeOfType SOT = SizeOfType::SizeOf); + /// Negate an APSInt in place, converting it to a signed form if necessary, and /// preserving its value (by extending by up to one bit as needed). static void negateAsSigned(APSInt &Int) { @@ -3222,14 +3233,9 @@ static bool HandleLValueIndirectMember(EvalInfo &Info, const Expr *E, return true; } -enum class SizeOfType { - SizeOf, - DataSizeOf, -}; - /// Get the size of the given type in char units. static bool HandleSizeof(EvalInfo &Info, SourceLocation Loc, QualType Type, - CharUnits &Size, SizeOfType SOT = SizeOfType::SizeOf) { + CharUnits &Size, SizeOfType SOT) { // sizeof(void), __alignof__(void), sizeof(function) = 1 as a gcc // extension. if (Type->isVoidType() || Type->isFunctionType()) { @@ -7884,6 +7890,522 @@ class ExprEvaluatorBase return StmtVisitorTy::Visit(Source); } + static bool EvaluateAtomicOrderToIgnore(const AtomicExpr *E, EvalInfo &Info) { + // we ignore results, but we need to evaluate them + [[maybe_unused]] APSInt OrderIgnoredResult; + + const Expr *OrderSuccess = E->getOrder(); + if (!EvaluateInteger(OrderSuccess, OrderIgnoredResult, Info)) + return false; + + if (E->isCmpXChg()) { + const Expr *OrderFail = E->getOrderFail(); + if (!EvaluateInteger(OrderFail, OrderIgnoredResult, Info)) + return false; + } + + return true; + } + + static bool EvaluateAtomicWeakToIgnore(const AtomicExpr *E, EvalInfo &Info) { + // we ignore results, but we need to evaluate them + [[maybe_unused]] APSInt WeakIgnoredResult; + + if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n || + E->getOp() == AtomicExpr::AO__atomic_compare_exchange) { + const Expr *Weak = E->getWeak(); + if (!EvaluateInteger(Weak, WeakIgnoredResult, Info)) + return false; + } + + return true; + } + + static bool LoadAtomicValue(const AtomicExpr *E, APValue &Result, + EvalInfo &Info) { + LValue AtomicStorageLV; + + if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info)) + return false; + + return handleLValueToRValueConversion(Info, E->getPtr(), E->getValueType(), + AtomicStorageLV, Result); + } + + static bool StoreValueIntoResultPointer(Expr *ResultPtr, + APValue &ValueToStore, + EvalInfo &Info) { + // TODO check it must be a pointer + assert(ResultPtr->getType()->isPointerType()); + QualType PointeeTy = ResultPtr->getType()->getPointeeType(); + LValue PointeeLV; + + if (!EvaluatePointer(ResultPtr, PointeeLV, Info)) + return false; + + return handleAssignment(Info, ResultPtr, PointeeLV, PointeeTy, + ValueToStore); + } + + static bool LoadAtomicValueInto(const AtomicExpr *E, EvalInfo &Info) { + APValue LocalResult; + + if (!LoadAtomicValue(E, LocalResult, Info)) + return false; + + if (!StoreValueIntoResultPointer(E->getVal1(), LocalResult, Info)) + return false; + + return true; + } + + static bool StoreAtomicValue(const AtomicExpr *E, EvalInfo &Info) { + LValue AtomicStorageLV; + + if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info)) + return false; + + APValue ProvidedValue; + + // GCC's atomic_store takes pointer to value, not value itself + if (E->getOp() == AtomicExpr::AO__atomic_store) { + LValue ProvidedLV; + if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info)) + return false; + + if (!handleLValueToRValueConversion(Info, E->getVal1(), + E->getVal1()->getType(), ProvidedLV, + ProvidedValue)) + return false; + + } else { + if (!Evaluate(ProvidedValue, Info, E->getVal1())) + return false; + } + if (!handleAssignment(Info, E, AtomicStorageLV, E->getValueType(), + ProvidedValue)) + return false; + + return true; + } + + static bool ExchangeAtomicValueInto(const AtomicExpr *E, EvalInfo &Info) { + assert(E->getOp() == AtomicExpr::AO__atomic_exchange); + // implementation of GCC's exchange (non _n version) + LValue AtomicStorageLV; + if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info)) + return false; + + // read previous value + APValue PreviousValue; + if (!handleLValueToRValueConversion(Info, E->getPtr(), E->getValueType(), + AtomicStorageLV, PreviousValue)) + return false; + + // get provided value from argument (pointer) + LValue ProvidedLV; + if (!EvaluatePointer(E->getVal1(), ProvidedLV, Info)) + return false; + + APValue ProvidedValue; + if (!handleLValueToRValueConversion(Info, E->getVal1(), + E->getVal1()->getType(), ProvidedLV, + ProvidedValue)) + return false; + + // store provided value to atomic value + if (!handleAssignment(Info, E, AtomicStorageLV, E->getValueType(), + ProvidedValue)) + return false; + + // store previous value in output pointer + if (!StoreValueIntoResultPointer(E->getVal2(), PreviousValue, Info)) + return false; + + return true; + } + + static bool FetchAtomicOp(const AtomicExpr *E, APValue &Result, + EvalInfo &Info, bool StoreToResultAfter) { + // read atomic + LValue AtomicStorageLV; + QualType AtomicValueTy = E->getValueType(); + if (!EvaluatePointer(E->getPtr(), AtomicStorageLV, Info)) + return false; + + APValue CurrentValue; + if (!handleLValueToRValueConversion(Info, E->getPtr(), E->getType(), + AtomicStorageLV, CurrentValue)) + return false; + + // store current value for fetch-OP operations + if (!StoreToResultAfter) + Result = CurrentValue; + + // read argument for fetch OP + APValue ArgumentVal; + if (!Evaluate(ArgumentVal, Info, E->getVal1())) + return false; + + // calculate new value + APValue Replacement; + if (AtomicValueTy->isIntegralOrEnumerationType()) { + // both arguments are integers + const APSInt AtomicInt = CurrentValue.getInt(); + const APSInt ArgumentInt = ArgumentVal.getInt(); + + switch (E->getOp()) { + case AtomicExpr::AO__c11_atomic_fetch_add: + case AtomicExpr::AO__atomic_fetch_add: + case AtomicExpr::AO__atomic_add_fetch: + Replacement = APValue(AtomicInt + ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_sub: + case AtomicExpr::AO__atomic_fetch_sub: + case AtomicExpr::AO__atomic_sub_fetch: + Replacement = APValue(AtomicInt - ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_and: + case AtomicExpr::AO__atomic_fetch_and: + case AtomicExpr::AO__atomic_and_fetch: + Replacement = APValue(AtomicInt & ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_or: + case AtomicExpr::AO__atomic_fetch_or: + case AtomicExpr::AO__atomic_or_fetch: + Replacement = APValue(AtomicInt | ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_xor: + case AtomicExpr::AO__atomic_fetch_xor: + case AtomicExpr::AO__atomic_xor_fetch: + Replacement = APValue(AtomicInt ^ ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_nand: + case AtomicExpr::AO__atomic_fetch_nand: + case AtomicExpr::AO__atomic_nand_fetch: + Replacement = APValue(~(AtomicInt & ArgumentInt)); + break; + case AtomicExpr::AO__c11_atomic_fetch_max: + case AtomicExpr::AO__atomic_fetch_max: + case AtomicExpr::AO__atomic_max_fetch: + Replacement = + APValue((AtomicInt > ArgumentInt) ? AtomicInt : ArgumentInt); + break; + case AtomicExpr::AO__c11_atomic_fetch_min: + case AtomicExpr::AO__atomic_fetch_min: + case AtomicExpr::AO__atomic_min_fetch: + Replacement = + APValue((AtomicInt < ArgumentInt) ? AtomicInt : ArgumentInt); + break; + default: + return false; + } + } else if (AtomicValueTy->isRealFloatingType()) { + // both arguments are float operations + const llvm::RoundingMode RM = getActiveRoundingMode(Info, E); + APFloat AtomicFlt = CurrentValue.getFloat(); + const APFloat ArgumentFlt = ArgumentVal.getFloat(); + APFloat::opStatus St; + + switch (E->getOp()) { + case AtomicExpr::AO__c11_atomic_fetch_add: // GCC atomics doesn't support + // floats + St = AtomicFlt.add(ArgumentFlt, RM); + Replacement = APValue(AtomicFlt); + break; + case AtomicExpr::AO__c11_atomic_fetch_sub: + St = AtomicFlt.subtract(ArgumentFlt, RM); + Replacement = APValue(AtomicFlt); + break; + default: + return false; + } + + if (!checkFloatingPointResult(Info, E, St)) + return false; + + } else if (AtomicValueTy->isPointerType()) { + // pointer + int arguments + LValue AtomicPtr; + AtomicPtr.setFrom(Info.Ctx, CurrentValue); + + APSInt ArgumentInt = ArgumentVal.getInt(); + + // calculate size of pointee object + CharUnits SizeOfPointee; + if (!HandleSizeof(Info, E->getExprLoc(), AtomicValueTy->getPointeeType(), + SizeOfPointee)) + return false; + + // GCC's atomic_fetch add/sub compute new pointer by bytes and not + // sizeof(T) + switch (E->getOp()) { + case AtomicExpr::AO__atomic_fetch_add: + case AtomicExpr::AO__atomic_add_fetch: + case AtomicExpr::AO__atomic_fetch_sub: + case AtomicExpr::AO__atomic_sub_fetch: { + const auto sizeOfOneItem = + APSInt(APInt(ArgumentInt.getBitWidth(), SizeOfPointee.getQuantity(), + false), + false); + // incrementing pointer by size which is not dividable by pointee size + // is UB and therefore disallowed + if ((ArgumentInt % sizeOfOneItem) != 0) + return false; + + ArgumentInt /= sizeOfOneItem; + } break; + default: + break; + } + + switch (E->getOp()) { + case AtomicExpr::AO__c11_atomic_fetch_add: + case AtomicExpr::AO__atomic_fetch_add: + case AtomicExpr::AO__atomic_add_fetch: + AtomicPtr.adjustOffsetAndIndex(Info, E, ArgumentInt, SizeOfPointee); + break; + case AtomicExpr::AO__c11_atomic_fetch_sub: + case AtomicExpr::AO__atomic_fetch_sub: + case AtomicExpr::AO__atomic_sub_fetch: + ArgumentInt.negate(); + AtomicPtr.adjustOffsetAndIndex(Info, E, ArgumentInt, SizeOfPointee); + break; + default: + return false; + } + + AtomicPtr.moveInto(Replacement); + } else { + // not float,int,pointer? + return false; + } + + // OP-fetch operation store result, not previous value + if (StoreToResultAfter) + Result = Replacement; + + // store result to atomic's storage + return handleAssignment(Info, E, AtomicStorageLV, AtomicValueTy, + Replacement); + } + + static bool CompareExchangeAtomicValue(const AtomicExpr *E, APValue &Result, + EvalInfo &Info) { + EvaluateAtomicWeakToIgnore(E, Info); + + // dereference _Atomic * (atomic value) + LValue AtomicLV; + QualType AtomicTy = E->getValueType(); + if (!EvaluatePointer(E->getPtr(), AtomicLV, Info)) { + return false; + } + + // dereference T * (expected value) + LValue ExpectedLV; + QualType ExpectedTy = E->getVal1()->getType()->getPointeeType(); + if (!EvaluatePointer(E->getVal1(), ExpectedLV, Info)) { + return false; + } + + // get values for atomic and expected + APValue AtomicVal; + APValue ExpectedVal; + + // convert pointer to value + if (!handleLValueToRValueConversion(Info, E->getPtr(), AtomicTy, AtomicLV, + AtomicVal)) { + return false; + } + + if (!handleLValueToRValueConversion(Info, E->getVal1(), ExpectedTy, + ExpectedLV, ExpectedVal)) { + return false; + } + + bool DoExchange = false; + + // compare atomic<int> and friends + if (AtomicTy->isIntegralOrEnumerationType() && + ExpectedTy->isIntegralOrEnumerationType()) { + const APSInt AtomicInt = AtomicVal.getInt(); + const APSInt ExpectedInt = ExpectedVal.getInt(); + if (AtomicInt == ExpectedInt) { + DoExchange = true; + } + } else if (AtomicTy->isRealFloatingType() && + ExpectedTy->isRealFloatingType()) { + const APFloat AtomicFlt = AtomicVal.getFloat(); + const APFloat ExpectedFlt = ExpectedVal.getFloat(); + if (AtomicFlt == ExpectedFlt) { + DoExchange = true; + } + } else if (AtomicTy->isPointerType() && ExpectedTy->isPointerType()) { + // get LValue of objects pointed to + LValue LHS; + LHS.setFrom(Info.Ctx, AtomicVal); + + LValue RHS; + RHS.setFrom(Info.Ctx, ExpectedVal); + + if (HasSameBase(LHS, RHS)) { + const CharUnits &LHSOffset = LHS.getLValueOffset(); + const CharUnits &RHSOffset = RHS.getLValueOffset(); + + const unsigned PtrSize = Info.Ctx.getTypeSize(AtomicTy); + assert(PtrSize <= 64 && "Pointer width is larger than expected"); + const uint64_t Mask = ~0ULL >> (64 - PtrSize); + + const uint64_t CompareLHS = LHSOffset.getQuantity() & Mask; + const uint64_t CompareRHS = RHSOffset.getQuantity() & Mask; + + if (CompareLHS == CompareRHS) { + DoExchange = true; + } + } else { + + // it's implementation-defined to compare distinct literals + // it's not constant-evaluation + if ((IsLiteralLValue(LHS) || IsLiteralLValue(RHS)) && LHS.Base && + RHS.Base) { + return false; + } + + if (IsWeakLValue(LHS) || IsWeakLValue(RHS)) { + return false; + } + + if ((!LHS.Base && !LHS.Offset.isZero()) || + (!RHS.Base && !RHS.Offset.isZero())) { + return false; + } + + if (LHS.Base && LHS.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, RHS)) { + return false; + } + + if (RHS.Base && RHS.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, LHS)) { + return false; + } + + if ((RHS.Base && isZeroSized(RHS)) || (LHS.Base && isZeroSized(LHS))) { + return false; + } + + // after all it's a different object + DoExchange = false; + } + + } else { + return false; + } + + APValue Replacement; + if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange) { + // GCC atomic_compare_exchange provides pointer and not value + LValue ReplacementLV; + if (!EvaluatePointer(E->getVal2(), ReplacementLV, Info)) + return false; + + if (!handleLValueToRValueConversion(Info, E->getVal2(), + E->getVal2()->getType(), + ReplacementLV, Replacement)) + return false; + + } else { + if (!Evaluate(Replacement, Info, E->getVal2())) { + return false; + } + } + + if (DoExchange) { + // if values are same do the exchange with replacement value + // but first I must evaluate the replacement value + + // and assign it to atomic + if (!handleAssignment(Info, E, AtomicLV, AtomicTy, Replacement)) { + return false; + } + } + + // to expected pointer I need to put previous value in atomic + if (!handleAssignment(Info, E, ExpectedLV, ExpectedTy, AtomicVal)) { + return false; + } + + // and return boolean if I did the exchange + Result = APValue(Info.Ctx.MakeIntValue(DoExchange, E->getType())); + return true; + } + + bool VisitAtomicExpr(const AtomicExpr *E) { + if (!EvaluateAtomicOrderToIgnore(E, Info)) + return false; + + APValue LocalResult; + switch (E->getOp()) { + default: + return Error(E); + case AtomicExpr::AO__c11_atomic_load: + case AtomicExpr::AO__atomic_load_n: + if (!LoadAtomicValue(E, LocalResult, Info)) { + return Error(E); + } + return DerivedSuccess(LocalResult, E); + case AtomicExpr::AO__c11_atomic_compare_exchange_strong: + case AtomicExpr::AO__c11_atomic_compare_exchange_weak: + case AtomicExpr::AO__atomic_compare_exchange: + case AtomicExpr::AO__atomic_compare_exchange_n: + if (!CompareExchangeAtomicValue(E, LocalResult, Info)) { + return Error(E); + } + return DerivedSuccess(LocalResult, E); + case AtomicExpr::AO__c11_atomic_exchange: + case AtomicExpr::AO__atomic_exchange_n: + if (!LoadAtomicValue(E, LocalResult, Info)) { + return Error(E); + } + if (!StoreAtomicValue(E, Info)) { + return Error(E); + } + return DerivedSuccess(LocalResult, E); + case AtomicExpr::AO__c11_atomic_fetch_add: + case AtomicExpr::AO__c11_atomic_fetch_sub: + case AtomicExpr::AO__c11_atomic_fetch_and: + case AtomicExpr::AO__c11_atomic_fetch_or: + case AtomicExpr::AO__c11_atomic_fetch_xor: + case AtomicExpr::AO__c11_atomic_fetch_nand: + case AtomicExpr::AO__c11_atomic_fetch_max: + case AtomicExpr::AO__c11_atomic_fetch_min: + case AtomicExpr::AO__atomic_fetch_add: + case AtomicExpr::AO__atomic_fetch_sub: + case AtomicExpr::AO__atomic_fetch_and: + case AtomicExpr::AO__atomic_fetch_xor: + case AtomicExpr::AO__atomic_fetch_or: + case AtomicExpr::AO__atomic_fetch_nand: + case AtomicExpr::AO__atomic_fetch_max: + case AtomicExpr::AO__atomic_fetch_min: + if (!FetchAtomicOp(E, LocalResult, Info, false)) { + return Error(E); + } + return DerivedSuccess(LocalResult, E); + case AtomicExpr::AO__atomic_add_fetch: + case AtomicExpr::AO__atomic_sub_fetch: + case AtomicExpr::AO__atomic_and_fetch: + case AtomicExpr::AO__atomic_xor_fetch: + case AtomicExpr::AO__atomic_or_fetch: + case AtomicExpr::AO__atomic_nand_fetch: + case AtomicExpr::AO__atomic_max_fetch: + case AtomicExpr::AO__atomic_min_fetch: + if (!FetchAtomicOp(E, LocalResult, Info, true)) { + return Error(E); + } + return DerivedSuccess(LocalResult, E); + } + } + bool VisitPseudoObjectExpr(const PseudoObjectExpr *E) { for (const Expr *SemE : E->semantics()) { if (auto *OVE = dyn_cast<OpaqueValueExpr>(SemE)) { @@ -15595,6 +16117,25 @@ class VoidExprEvaluator } } + bool VisitAtomicExpr(const AtomicExpr *E) { + if (!EvaluateAtomicOrderToIgnore(E, Info)) + return false; + + switch (E->getOp()) { + default: + return Error(E); + case AtomicExpr::AO__atomic_load: + return LoadAtomicValueInto(E, Info); + case AtomicExpr::AO__atomic_exchange: + return ExchangeAtomicValueInto(E, Info); + case AtomicExpr::AO__c11_atomic_init: + case AtomicExpr::AO__c11_atomic_store: + case AtomicExpr::AO__atomic_store: + case AtomicExpr::AO__atomic_store_n: + return StoreAtomicValue(E, Info); + } + } + bool VisitCallExpr(const CallExpr *E) { if (!IsConstantEvaluatedBuiltinCall(E)) return ExprEvaluatorBaseTy::VisitCallExpr(E); diff --git a/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp new file mode 100644 index 0000000000000..3dc18b3d7c730 --- /dev/null +++ b/clang/test/SemaCXX/atomic-constexpr-c11-builtins.cpp @@ -0,0 +1,288 @@ +// RUN: %clang_cc1 -std=c++20 %s + +// expected-no-diagnostics + +constexpr int int_min = -2147483648; +constexpr int int_max = 2147483647; + +const int array[2] = {1,2}; +const char small_array[2] = {1,2}; + +template <typename T> struct identity { + using type = T; +}; + +template <typename T> using do_not_deduce = typename identity<T>::type; + +// -- LOAD -- + +template <typename T> consteval T load(T value) { + _Atomic(T) av = value; + return __c11_atomic_load(&av, __ATOMIC_RELAXED); +} + +// integers +static_assert(load(true) == true); +static_assert(load(false) == false); + +static_assert(load(42) == 42); +static_assert(load(-128) == -128); + +static_assert(load(42u) == 42u); +static_assert(load(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// floats +static_assert(load(42.3) == 42.3); +static_assert(load(42.3f) == 42.3f); + +// pointers +static_assert(load(&array[0]) == &array[0]); +static_assert(load(&small_array[1]) == &small_array[1]); + +// -- STORE -- + +template <typename T> consteval T store(T value) { + _Atomic(T) av = T{}; + __c11_atomic_store(&av, value, __ATOMIC_RELAXED); + return __c11_atomic_load(&av, __ATOMIC_RELAXED); +} + +// integers +static_assert(store(true) == true); +static_assert(store(false) == false); + +static_assert(store(42) == 42); +static_assert(store(-128) == -128); + +static_assert(store(42u) == 42u); +static_assert(store(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// floats +static_assert(store(42.3) == 42.3); +static_assert(store(42.3f) == 42.3f); + +// pointers +static_assert(store(&array[0]) == &array[0]); +static_assert(store(&small_array[1]) == &small_array[1]); + +// -- EXCHANGE -- +template <typename T> struct two_values { + T before; + T after; + constexpr friend bool operator==(two_values, two_values) = default; +}; + +template <typename T> consteval auto exchange(T value, do_not_deduce<T> replacement) -> two_values<T> { + _Atomic(T) av = T{value}; + T previous = __c11_atomic_exchange(&av, replacement, __ATOMIC_RELAXED); + return two_values<T>{previous, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(exchange(true,false) == two_values{true, false}); +static_assert(exchange(false,true) == two_values{false, true}); + +static_assert(exchange(10,42) == two_values{10,42}); +static_assert(exchange(14,-128) == two_values{14,-128}); + + +static_assert(exchange(56u,42u) == two_values{56u,42u}); +static_assert(exchange(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu}); + +// floats +static_assert(exchange(42.3,1.2) == two_values{42.3,1.2}); +static_assert(exchange(42.3f,-16.7f) == two_values{42.3f, -16.7f}); + +// pointers +static_assert(exchange(&array[0], &array[1]) == two_values{&array[0],&array[1]}); +static_assert(exchange(&small_array[1], &small_array[0]) == two_values{&small_array[1], &small_array[0]}); + +// -- COMPARE-EXCHANGE -- +template <typename T> struct comp_result { + bool success; + T output; + T after; + + constexpr comp_result(bool success_, T output_, do_not_deduce<T> after_): success{success_}, output{output_}, after{after_} { } + + constexpr friend bool operator==(comp_result, comp_result) = default; +}; + +template <typename T> constexpr auto comp_success(T output, do_not_deduce<T> after) { + return comp_result<T>{true, output, after}; +} + +template <typename T> constexpr auto comp_failure(T after) { + return comp_result<T>{false, after, after}; +} + +template <typename T> consteval auto compare_exchange_weak(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + _Atomic(T) av = T{original}; + const bool success = __c11_atomic_compare_exchange_weak(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_weak(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_weak(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_weak(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_weak(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_weak(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_weak(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_weak(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_weak(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_weak(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_weak(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_weak(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_weak(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// floats +static_assert(compare_exchange_weak(42.3, 42.3,1.2) == comp_success(42.3,1.2)); +static_assert(compare_exchange_weak(42.3f, 42.3f,-16.7f) == comp_success(42.3f, -16.7f)); +static_assert(compare_exchange_weak(0.0, 42.3,1.2) == comp_failure(0.0)); +static_assert(compare_exchange_weak(0.0f, 42.3f,-16.7f) == comp_failure(0.0f)); + +// pointers +static_assert(compare_exchange_weak(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_weak(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_weak(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_weak(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + + +template <typename T> consteval auto compare_exchange_strong(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + _Atomic(T) av = T{original}; + const bool success = __c11_atomic_compare_exchange_strong(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_strong(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_strong(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_strong(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_strong(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_strong(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_strong(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_strong(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_strong(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_strong(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_strong(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_strong(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_strong(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// floats +static_assert(compare_exchange_strong(42.3, 42.3,1.2) == comp_success(42.3,1.2)); +static_assert(compare_exchange_strong(42.3f, 42.3f,-16.7f) == comp_success(42.3f, -16.7f)); +static_assert(compare_exchange_strong(0.0, 42.3,1.2) == comp_failure(0.0)); +static_assert(compare_exchange_strong(0.0f, 42.3f,-16.7f) == comp_failure(0.0f)); + +// pointers +static_assert(compare_exchange_strong(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_strong(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_strong(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_strong(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + + +// --FETCH-OP-- +template <typename T, typename Y> consteval auto fetch_add(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(fetch_add(false, 1) == two_values{false, true}); +static_assert(fetch_add(0, 100) == two_values{0, 100}); +static_assert(fetch_add(100, -50) == two_values{100, 50}); + +static_assert(fetch_add(int_max, 1) == two_values{int_max, int_min}); // overflow is defined for atomic +static_assert(fetch_add(int_min, -1) == two_values{int_min, int_max}); + +// floats +static_assert(fetch_add(10.3, 2.1) == two_values{10.3, 12.4}); +static_assert(fetch_add(10.3f, 2.1f) == two_values{10.3f, 12.4f}); + +// pointers +static_assert(fetch_add(&array[0], 1) == two_values{&array[0], &array[1]}); +static_assert(fetch_add(&small_array[0], 1) == two_values{&small_array[0], &small_array[1]}); +static_assert(fetch_add(&array[1], 0) == two_values{&array[1], &array[1]}); +static_assert(fetch_add(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]}); +static_assert(fetch_add(&array[1], -1) == two_values{&array[1], &array[0]}); +static_assert(fetch_add(&small_array[1], -1) == two_values{&small_array[1], &small_array[0]}); + +template <typename T, typename Y> consteval auto fetch_sub(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(fetch_sub(true, 1) == two_values{true, false}); +static_assert(fetch_sub(0, 100) == two_values{0, -100}); +static_assert(fetch_sub(100, -50) == two_values{100, 150}); + +static_assert(fetch_sub(int_min, 1) == two_values{int_min, int_max}); // overflow is defined for atomic +static_assert(fetch_sub(int_max, -1) == two_values{int_max, int_min}); + +// floats +static_assert(fetch_sub(10.3, 2.3) == two_values{10.3, 8.0}); +static_assert(fetch_sub(10.3f, 2.3f) == two_values{10.3f, 8.0f}); + +// pointers +static_assert(fetch_sub(&array[1], 1) == two_values{&array[1], &array[0]}); +static_assert(fetch_sub(&small_array[1], 1) == two_values{&small_array[1], &small_array[0]}); +static_assert(fetch_sub(&array[1], 0) == two_values{&array[1], &array[1]}); +static_assert(fetch_sub(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]}); +static_assert(fetch_sub(&array[0], -1) == two_values{&array[0], &array[1]}); +static_assert(fetch_sub(&small_array[0], -1) == two_values{&small_array[0], &small_array[1]}); + +template <typename T, typename Y> consteval auto fetch_and(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_and(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_or(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_or(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_xor(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_nand(T original, Y arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +static_assert(fetch_and(0b1101u, 0b1011u) == two_values{0b1101u, 0b1001u}); +static_assert(fetch_or(0b1101u, 0b1011u) == two_values{0b1101u, 0b1111u}); +static_assert(fetch_xor(0b1101u, 0b1011u) == two_values{0b1101u, 0b0110u}); +static_assert(fetch_nand(0b1001u, 0b1011u) == two_values{0b1001u, 0xFFFFFFF6u}); + +template <typename T> consteval auto fetch_min(T original, T arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_min(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template <typename T> consteval auto fetch_max(T original, T arg) -> two_values<T> { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_max(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +static_assert(fetch_max(10, 16) == two_values{10, 16}); +static_assert(fetch_max(16, 10) == two_values{16, 16}); + +static_assert(fetch_min(10, 16) == two_values{10, 10}); +static_assert(fetch_min(16, 10) == two_values{16, 10}); + diff --git a/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp new file mode 100644 index 0000000000000..1e9ff93e313d6 --- /dev/null +++ b/clang/test/SemaCXX/atomic-constexpr-gcc-builtins.cpp @@ -0,0 +1,494 @@ +// RUN: %clang_cc1 -std=c++20 %s + +// expected-no-diagnostics + +constexpr int int_min = -2147483648; +constexpr int int_max = 2147483647; + +const int array[2] = {1,2}; +const char small_array[2] = {1,2}; + +template <typename T> struct identity { + using type = T; +}; + +template <typename T> using do_not_deduce = typename identity<T>::type; + +// -- LOAD -- + +template <typename T> consteval T load(T value) { + T av = value; + T out{}; + __atomic_load(&av, &out, __ATOMIC_RELAXED); + return out; +} + +// integers +static_assert(load(true) == true); +static_assert(load(false) == false); + +static_assert(load(42) == 42); +static_assert(load(-128) == -128); + +static_assert(load(42u) == 42u); +static_assert(load(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// pointers +static_assert(load(&array[0]) == &array[0]); +static_assert(load(&small_array[1]) == &small_array[1]); + +// -- LOAD-N -- + +template <typename T> consteval T load_n(T value) { + T av = value; + return __atomic_load_n(&av, __ATOMIC_RELAXED); +} + +// integers +static_assert(load_n(true) == true); +static_assert(load_n(false) == false); + +static_assert(load_n(42) == 42); +static_assert(load_n(-128) == -128); + +static_assert(load_n(42u) == 42u); +static_assert(load_n(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// pointers +static_assert(load_n(&array[0]) == &array[0]); +static_assert(load_n(&small_array[1]) == &small_array[1]); + + +// -- STORE -- + +template <typename T> consteval T store(T value) { + T av = T{}; + __atomic_store(&av, &value, __ATOMIC_RELAXED); + return __atomic_load_n(&av, __ATOMIC_RELAXED); +} + +// integers +static_assert(store(true) == true); +static_assert(store(false) == false); + +static_assert(store(42) == 42); +static_assert(store(-128) == -128); + +static_assert(store(42u) == 42u); +static_assert(store(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// pointers +static_assert(store(&array[0]) == &array[0]); +static_assert(store(&small_array[1]) == &small_array[1]); + +// -- STORE-N -- + +template <typename T> consteval T store_n(T value) { + T av = T{}; + __atomic_store_n(&av, value, __ATOMIC_RELAXED); + return __atomic_load_n(&av, __ATOMIC_RELAXED); +} + +// integers +static_assert(store_n(true) == true); +static_assert(store_n(false) == false); + +static_assert(store_n(42) == 42); +static_assert(store_n(-128) == -128); + +static_assert(store_n(42u) == 42u); +static_assert(store_n(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// pointers +static_assert(store_n(&array[0]) == &array[0]); +static_assert(store_n(&small_array[1]) == &small_array[1]); + +// -- EXCHANGE -- +template <typename T> struct two_values { + T before; + T after; + constexpr friend bool operator==(two_values, two_values) = default; +}; + +template <typename T> consteval auto exchange(T value, do_not_deduce<T> replacement) -> two_values<T> { + T av = T{value}; + T out{}; + __atomic_exchange(&av, &replacement, &out, __ATOMIC_RELAXED); + return two_values<T>{out, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(exchange(true,false) == two_values{true, false}); +static_assert(exchange(false,true) == two_values{false, true}); + +static_assert(exchange(10,42) == two_values{10,42}); +static_assert(exchange(14,-128) == two_values{14,-128}); + + +static_assert(exchange(56u,42u) == two_values{56u,42u}); +static_assert(exchange(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu}); + +// -- EXCHANGE-N -- +template <typename T> consteval auto exchange_n(T value, do_not_deduce<T> replacement) -> two_values<T> { + T av = T{value}; + T previous = __atomic_exchange_n(&av, replacement, __ATOMIC_RELAXED); + return two_values<T>{previous, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(exchange_n(true,false) == two_values{true, false}); +static_assert(exchange_n(false,true) == two_values{false, true}); + +static_assert(exchange_n(10,42) == two_values{10,42}); +static_assert(exchange_n(14,-128) == two_values{14,-128}); + + +static_assert(exchange_n(56u,42u) == two_values{56u,42u}); +static_assert(exchange_n(0xFFu, 0xFFFFFFFFu) == two_values{0xFFu,0xFFFFFFFFu}); + +// pointers +static_assert(exchange_n(&array[0], &array[1]) == two_values{&array[0],&array[1]}); +static_assert(exchange_n(&small_array[1], &small_array[0]) == two_values{&small_array[1], &small_array[0]}); + +// -- COMPARE-EXCHANGE -- +template <typename T> struct comp_result { + bool success; + T output; + T after; + + constexpr comp_result(bool success_, T output_, do_not_deduce<T> after_): success{success_}, output{output_}, after{after_} { } + + constexpr friend bool operator==(comp_result, comp_result) = default; +}; + +template <typename T> constexpr auto comp_success(T output, do_not_deduce<T> after) { + return comp_result<T>{true, output, after}; +} + +template <typename T> constexpr auto comp_failure(T after) { + return comp_result<T>{false, after, after}; +} + +template <typename T> consteval auto compare_exchange_weak(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + T av = T{original}; + const bool success = __atomic_compare_exchange(&av, &expected, &replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_weak(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_weak(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_weak(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_weak(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_weak(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_weak(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_weak(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_weak(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_weak(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_weak(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_weak(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_weak(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// pointers +static_assert(compare_exchange_weak(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_weak(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_weak(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_weak(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + + +template <typename T> consteval auto compare_exchange_strong(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + T av = T{original}; + const bool success = __atomic_compare_exchange(&av, &expected, &replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_strong(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_strong(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_strong(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_strong(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_strong(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_strong(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_strong(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_strong(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_strong(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_strong(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_strong(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_strong(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// pointers +static_assert(compare_exchange_strong(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_strong(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_strong(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_strong(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + +// --COMPARE-EXCHANGE-N-- + +template <typename T> consteval auto compare_exchange_weak_n(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + T av = T{original}; + const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_weak_n(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_weak_n(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_weak_n(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_weak_n(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_weak_n(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_weak_n(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_weak_n(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_weak_n(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_weak_n(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_weak_n(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_weak_n(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_weak_n(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// pointers +static_assert(compare_exchange_weak_n(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_weak_n(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_weak_n(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_weak_n(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + + +template <typename T> consteval auto compare_exchange_strong_n(T original, do_not_deduce<T> expected, do_not_deduce<T> replacement) -> comp_result<T> { + T av = T{original}; + const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result<T>{success, expected, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(compare_exchange_strong_n(true, true, false) == comp_success(true, false)); +static_assert(compare_exchange_strong_n(false, false, true) == comp_success(false, true)); +static_assert(compare_exchange_strong_n(false, true, false) == comp_failure(false)); +static_assert(compare_exchange_strong_n(true, false, true) == comp_failure(true)); + +static_assert(compare_exchange_strong_n(10,10,42) == comp_success(10,42)); +static_assert(compare_exchange_strong_n(14,14,-128) == comp_success(14,-128)); +static_assert(compare_exchange_strong_n(-10,10,42) == comp_failure(-10)); +static_assert(compare_exchange_strong_n(-14,14,-128) == comp_failure(-14)); + +static_assert(compare_exchange_strong_n(56u, 56u,42u) == comp_success(56u,42u)); +static_assert(compare_exchange_strong_n(0xFFu, 0xFFu, 0xFFFFFFFFu) == comp_success(0xFFu,0xFFFFFFFFu)); +static_assert(compare_exchange_strong_n(3u, 56u,42u) == comp_failure(3u)); +static_assert(compare_exchange_strong_n(0xFu, 0xFFu, 0xFFFFFFFFu) == comp_failure(0xFu)); + +// pointers +static_assert(compare_exchange_strong_n(&array[0], &array[0], &array[1]) == comp_success(&array[0],&array[1])); +static_assert(compare_exchange_strong_n(&small_array[1], &small_array[1], &small_array[0]) == comp_success(&small_array[1], &small_array[0])); +static_assert(compare_exchange_strong_n(&array[1], &array[0], &array[1]) == comp_failure(&array[1])); +static_assert(compare_exchange_strong_n(&small_array[0], &small_array[1], &small_array[0]) == comp_failure(&small_array[0])); + +// --FETCH-OP-- +template <typename T, typename Y> consteval auto fetch_add(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_add_ptr(T original, Y arg) -> two_values<T> { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr)); + arg *= pointee_size; + const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(fetch_add(false, 1) == two_values{false, true}); +static_assert(fetch_add(0, 100) == two_values{0, 100}); +static_assert(fetch_add(100, -50) == two_values{100, 50}); + +static_assert(fetch_add(int_max, 1) == two_values{int_max, int_min}); // overflow is defined for atomic +static_assert(fetch_add(int_min, -1) == two_values{int_min, int_max}); + +// pointers +static_assert(fetch_add_ptr(&array[0], 1) == two_values{&array[0], &array[1]}); +static_assert(fetch_add_ptr(&small_array[0], 1) == two_values{&small_array[0], &small_array[1]}); +static_assert(fetch_add_ptr(&array[1], 0) == two_values{&array[1], &array[1]}); +static_assert(fetch_add_ptr(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]}); +static_assert(fetch_add_ptr(&array[1], -1) == two_values{&array[1], &array[0]}); +static_assert(fetch_add_ptr(&small_array[1], -1) == two_values{&small_array[1], &small_array[0]}); + +template <typename T, typename Y> consteval auto fetch_sub(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_sub_ptr(T original, Y arg) -> two_values<T> { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr)); + arg *= pointee_size; + const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +// integers +static_assert(fetch_sub(true, 1) == two_values{true, false}); +static_assert(fetch_sub(0, 100) == two_values{0, -100}); +static_assert(fetch_sub(100, -50) == two_values{100, 150}); + +static_assert(fetch_sub(int_min, 1) == two_values{int_min, int_max}); // overflow is defined for atomic +static_assert(fetch_sub(int_max, -1) == two_values{int_max, int_min}); + +// pointers +static_assert(fetch_sub_ptr(&array[1], 1) == two_values{&array[1], &array[0]}); +static_assert(fetch_sub_ptr(&small_array[1], 1) == two_values{&small_array[1], &small_array[0]}); +static_assert(fetch_sub_ptr(&array[1], 0) == two_values{&array[1], &array[1]}); +static_assert(fetch_sub_ptr(&small_array[1], 0) == two_values{&small_array[1], &small_array[1]}); +static_assert(fetch_sub_ptr(&array[0], -1) == two_values{&array[0], &array[1]}); +static_assert(fetch_sub_ptr(&small_array[0], -1) == two_values{&small_array[0], &small_array[1]}); + +template <typename T, typename Y> consteval auto fetch_and(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_and(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_or(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_or(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_xor(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T, typename Y> consteval auto fetch_nand(T original, Y arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +static_assert(fetch_and(0b1101u, 0b1011u) == two_values{0b1101u, 0b1001u}); +static_assert(fetch_or(0b1101u, 0b1011u) == two_values{0b1101u, 0b1111u}); +static_assert(fetch_xor(0b1101u, 0b1011u) == two_values{0b1101u, 0b0110u}); +static_assert(fetch_nand(0b1001u, 0b1011u) == two_values{0b1001u, 0xFFFFFFF6u}); + +template <typename T> consteval auto fetch_min(T original, T arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_min(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template <typename T> consteval auto fetch_max(T original, T arg) -> two_values<T> { + T av = T{original}; + const T result = __atomic_fetch_max(&av, arg, __ATOMIC_RELAXED); + return two_values<T>{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +static_assert(fetch_max(10, 16) == two_values{10, 16}); +static_assert(fetch_max(16, 10) == two_values{16, 16}); + +static_assert(fetch_min(10, 16) == two_values{10, 10}); +static_assert(fetch_min(16, 10) == two_values{16, 10}); + +// --OP-FETCHP-- +template <typename T, typename Y> consteval auto add_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_add_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T, typename Y> consteval auto add_fetch_ptr(T original, Y arg) -> T { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr)); + arg *= pointee_size; + return __atomic_add_fetch(&av, arg, __ATOMIC_RELAXED); +} + +// integers +static_assert(add_fetch(false, 1) == true); +static_assert(add_fetch(0, 100) == 100); +static_assert(add_fetch(100, -50) == 50); + +static_assert(add_fetch(int_max, 1) == int_min); // overflow is defined for atomic +static_assert(add_fetch(int_min, -1) == int_max); + +// pointers +static_assert(add_fetch_ptr(&array[0], 1) == &array[1]); +static_assert(add_fetch_ptr(&small_array[0], 1) == &small_array[1]); +static_assert(add_fetch_ptr(&array[1], 0) == &array[1]); +static_assert(add_fetch_ptr(&small_array[1], 0) == &small_array[1]); +static_assert(add_fetch_ptr(&array[1], -1) == &array[0]); +static_assert(add_fetch_ptr(&small_array[1], -1) ==&small_array[0]); + +template <typename T, typename Y> consteval auto sub_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_sub_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T, typename Y> consteval auto sub_fetch_ptr(T original, Y arg) -> T { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast<T>(nullptr)); + arg *= pointee_size; + return __atomic_sub_fetch(&av, arg, __ATOMIC_RELAXED); +} + +// integers +static_assert(sub_fetch(true, 1) == false); +static_assert(sub_fetch(0, 100) == -100); +static_assert(sub_fetch(100, -50) == 150); + +static_assert(sub_fetch(int_min, 1) == int_max); // overflow is defined for atomic +static_assert(sub_fetch(int_max, -1) == int_min); + +// pointers +static_assert(sub_fetch_ptr(&array[1], 1) == &array[0]); +static_assert(sub_fetch_ptr(&small_array[1], 1) == &small_array[0]); +static_assert(sub_fetch_ptr(&array[1], 0) == &array[1]); +static_assert(sub_fetch_ptr(&small_array[1], 0) == &small_array[1]); +static_assert(sub_fetch_ptr(&array[0], -1) == &array[1]); +static_assert(sub_fetch_ptr(&small_array[0], -1) == &small_array[1]); + +template <typename T, typename Y> consteval auto and_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_and_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T, typename Y> consteval auto or_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_or_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T, typename Y> consteval auto xor_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_xor_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T, typename Y> consteval auto nand_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_nand_fetch(&av, arg, __ATOMIC_RELAXED); +} + +static_assert(and_fetch(0b1101u, 0b1011u) == 0b1001u); +static_assert(or_fetch(0b1101u, 0b1011u) == 0b1111u); +static_assert(xor_fetch(0b1101u, 0b1011u) == 0b0110u); +static_assert(nand_fetch(0b1001u, 0b1011u) == 0xFFFFFFF6u); + +template <typename T> consteval auto min_fetch(T original, T arg) -> T { + T av = T{original}; + return __atomic_min_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template <typename T> consteval auto max_fetch(T original, T arg) -> T { + T av = T{original}; + return __atomic_max_fetch(&av, arg, __ATOMIC_RELAXED); +} + +static_assert(max_fetch(10, 16) == 16); +static_assert(max_fetch(16, 10) == 16); + +static_assert(min_fetch(10, 16) == 10); +static_assert(min_fetch(16, 10) == 10); + + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits