diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index e020710c7aa4f..a703606e4c0c2 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -4340,6 +4340,18 @@ whole system, the current device, an OpenCL workgroup, wavefront, or just a single thread. If these are used on a target that does not support atomic scopes, then they will behave exactly as the standard GNU atomic builtins. +Constant evaluatable atomic builtins +------------------------------------ + +Clang supports constant evaluation of all ``__c11_atomic_*`` and GCC-compatible +``__atomic_*`` builtins. Behaviour of identical as if evaluating these builtins +in a single-threaded environment. + +(Note the GCC-compatible ``__atomic_fetch_OP`` and ``__atomic_OP_fetch`` +builtins with a pointer argument operates as if pointer was pointing to bytes. +Evaluating these builtins which would result in a non-aligned pointer to pointee +type is unsupported.) + Low-level ARM exclusive memory builtins --------------------------------------- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index acd9dd9298ce1..e60974fb5cf7a 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -266,6 +266,9 @@ C++2c Feature Support - Implemented `P3176R1 The Oxford variadic comma `_ +- Making GCC and C11 atomic intrinsics evaluatable during constant evaluation, + which supports `P3309R3 constexpr atomic and atomic_ref `_ + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ - Removed the restriction to literal types in constexpr functions in C++23 mode. diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 708c8656decbe..3fc8062783b8b 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -6830,6 +6830,9 @@ class AtomicExpr : public Expr { std::unique_ptr getScopeModel() const { return getScopeModel(getOp()); } + + /// Helper function to check valid ordering for specified Op. + static bool isValidOrderingForOp(int64_t Ordering, AtomicExpr::AtomicOp Op); }; /// TypoExpr - Internal placeholder for expressions where typo correction diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 468c16050e2bf..797191fba888d 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1744,97 +1744,97 @@ def SyncSwapN : Builtin, SyncBuiltinsTemplate { // C11 _Atomic operations for . 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)"; } @@ -1847,133 +1847,133 @@ 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(...)"; } @@ -1991,7 +1991,7 @@ def AtomicClear : Builtin { def AtomicThreadFence : Builtin { let Spellings = ["__atomic_thread_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } @@ -2003,7 +2003,7 @@ def ScopedAtomicThreadFence : Builtin { def AtomicSignalFence : Builtin { let Spellings = ["__atomic_signal_fence"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; let Prototype = "void(int)"; } diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index f630698757c5f..db8ddaf0ff9d5 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -389,6 +389,12 @@ def note_constexpr_assumption_failed : Note< "assumption evaluated to false">; def err_experimental_clang_interp_failed : Error< "the experimental clang interpreter failed to evaluate an expression">; +def note_unaligned_atomic_pointer_op : Note< + "atomic pointer operation with argument %0 not aligned to size of pointee type (sizeof %1 is %2)">; +def note_constexpr_atomic_ops_only_in_cpp : Note< + "constant evaluated atomic operations are supported only in C++ mode">; +def note_atomic_op_with_invalid_memory_order : Note< + "evaluated %select{|success |failure }0memory order argument to atomic operation is not allowed">; def warn_integer_constant_overflow : Warning< "overflow in expression; result is %0 with type %1">, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index e220f69b3a4f5..4f916b1df06b1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -1921,6 +1921,20 @@ static bool EvaluateFixedPointOrInteger(const Expr *E, APFixedPoint &Result, static bool EvaluateFixedPoint(const Expr *E, APFixedPoint &Result, EvalInfo &Info); +/// Support for atomic builtins. +static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info); +static bool EvaluateAtomicLoad(const AtomicExpr *E, APValue &Result, + EvalInfo &Info); +static bool EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info); +static bool EvaluateAtomicStore(const AtomicExpr *E, EvalInfo &Info); +static bool EvaluateAtomicExchange(const AtomicExpr *E, APValue &Result, + EvalInfo &Info); +static bool EvaluateAtomicExchangeInto(const AtomicExpr *E, EvalInfo &Info); +static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result, + EvalInfo &Info, bool StoreToResultAfter); +static bool EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result, + EvalInfo &Info); + //===----------------------------------------------------------------------===// // Misc utilities //===----------------------------------------------------------------------===// @@ -8075,6 +8089,64 @@ class ExprEvaluatorBase return StmtVisitorTy::Visit(Source); } + bool VisitAtomicExpr(const AtomicExpr *E) { + if (!EvaluateAtomicOrder(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 (!EvaluateAtomicLoad(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 (!EvaluateAtomicCompareExchange(E, LocalResult, Info)) + return Error(E); + return DerivedSuccess(LocalResult, E); + case AtomicExpr::AO__c11_atomic_exchange: + case AtomicExpr::AO__atomic_exchange_n: + if (!EvaluateAtomicExchange(E, LocalResult, 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 (!EvaluateAtomicFetchOp(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 (!EvaluateAtomicFetchOp(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(SemE)) { @@ -14127,6 +14199,158 @@ enum class CmpResult { }; } +template +static bool EvaluateLValueComparison(EvalInfo &Info, const Expr *E, + BinaryOperator::Opcode Opcode, + QualType LHSTy, LValue &LHSValue, + QualType RHSTy, LValue &RHSValue, + SuccessCB &&Success) { + auto Error = [&](const Expr *E) { + Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); + return false; + }; + + // Reject differing bases from the normal codepath; we special-case + // comparisons to null. + if (!HasSameBase(LHSValue, RHSValue)) { + auto DiagComparison = [&](unsigned DiagID, bool Reversed = false) { + std::string LHS = LHSValue.toString(Info.Ctx, LHSTy); + std::string RHS = RHSValue.toString(Info.Ctx, RHSTy); + Info.FFDiag(E, DiagID) + << (Reversed ? RHS : LHS) << (Reversed ? LHS : RHS); + return false; + }; + // Inequalities and subtractions between unrelated pointers have + // unspecified or undefined behavior. + if (!BinaryOperator::isEqualityOp(Opcode)) + return DiagComparison( + diag::note_constexpr_pointer_comparison_unspecified); + // A constant address may compare equal to the address of a symbol. + // The one exception is that address of an object cannot compare equal + // to a null pointer constant. + // TODO: Should we restrict this to actual null pointers, and exclude the + // case of zero cast to pointer type? + if ((!LHSValue.Base && !LHSValue.Offset.isZero()) || + (!RHSValue.Base && !RHSValue.Offset.isZero())) + return DiagComparison(diag::note_constexpr_pointer_constant_comparison, + !RHSValue.Base); + // C++2c [intro.object]/10: + // Two objects [...] may have the same address if [...] they are both + // potentially non-unique objects. + // C++2c [intro.object]/9: + // An object is potentially non-unique if it is a string literal object, + // the backing array of an initializer list, or a subobject thereof. + // + // This makes the comparison result unspecified, so it's not a constant + // expression. + // + // TODO: Do we need to handle the initializer list case here? + if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue)) + return DiagComparison(diag::note_constexpr_literal_comparison); + if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue)) + return DiagComparison(diag::note_constexpr_opaque_call_comparison, + !IsOpaqueConstantCall(LHSValue)); + // We can't tell whether weak symbols will end up pointing to the same + // object. + if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue)) + return DiagComparison(diag::note_constexpr_pointer_weak_comparison, + !IsWeakLValue(LHSValue)); + // We can't compare the address of the start of one object with the + // past-the-end address of another object, per C++ DR1652. + if (LHSValue.Base && LHSValue.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue)) + return DiagComparison(diag::note_constexpr_pointer_comparison_past_end, + true); + if (RHSValue.Base && RHSValue.Offset.isZero() && + isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue)) + return DiagComparison(diag::note_constexpr_pointer_comparison_past_end, + false); + // We can't tell whether an object is at the same address as another + // zero sized object. + if ((RHSValue.Base && isZeroSized(LHSValue)) || + (LHSValue.Base && isZeroSized(RHSValue))) + return DiagComparison(diag::note_constexpr_pointer_comparison_zero_sized); + return Success(CmpResult::Unequal); + } + + const CharUnits &LHSOffset = LHSValue.getLValueOffset(); + const CharUnits &RHSOffset = RHSValue.getLValueOffset(); + + SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); + SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); + + // C++11 [expr.rel]p2: + // - If two pointers point to non-static data members of the same object, + // or to subobjects or array elements fo such members, recursively, the + // pointer to the later declared member compares greater provided the + // two members have the same access control and provided their class is + // not a union. + // [...] + // - Otherwise pointer comparisons are unspecified. + if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && + BinaryOperator::isRelationalOp(Opcode)) { + bool WasArrayIndex; + unsigned Mismatch = FindDesignatorMismatch( + getType(LHSValue.Base), LHSDesignator, RHSDesignator, WasArrayIndex); + // At the point where the designators diverge, the comparison has a + // specified value if: + // - we are comparing array indices + // - we are comparing fields of a union, or fields with the same access + // Otherwise, the result is unspecified and thus the comparison is not a + // constant expression. + if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() && + Mismatch < RHSDesignator.Entries.size()) { + const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]); + const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]); + if (!LF && !RF) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes); + else if (!LF) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) + << getAsBaseClass(LHSDesignator.Entries[Mismatch]) + << RF->getParent() << RF; + else if (!RF) + Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) + << getAsBaseClass(RHSDesignator.Entries[Mismatch]) + << LF->getParent() << LF; + else if (!LF->getParent()->isUnion() && + LF->getAccess() != RF->getAccess()) + Info.CCEDiag(E, + diag::note_constexpr_pointer_comparison_differing_access) + << LF << LF->getAccess() << RF << RF->getAccess() + << LF->getParent(); + } + } + + // The comparison here must be unsigned, and performed with the same + // width as the pointer. + unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy); + uint64_t CompareLHS = LHSOffset.getQuantity(); + uint64_t CompareRHS = RHSOffset.getQuantity(); + assert(PtrSize <= 64 && "Unexpected pointer width"); + uint64_t Mask = ~0ULL >> (64 - PtrSize); + CompareLHS &= Mask; + CompareRHS &= Mask; + + // If there is a base and this is a relational operator, we can only + // compare pointers within the object in question; otherwise, the result + // depends on where the object is located in memory. + if (!LHSValue.Base.isNull() && BinaryOperator::isRelationalOp(Opcode)) { + QualType BaseTy = getType(LHSValue.Base); + if (BaseTy->isIncompleteType()) + return Error(E); + CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy); + uint64_t OffsetLimit = Size.getQuantity(); + if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit) + return Error(E); + } + + if (CompareLHS < CompareRHS) + return Success(CmpResult::Less); + if (CompareLHS > CompareRHS) + return Success(CmpResult::Greater); + return Success(CmpResult::Equal); +} + template static bool EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, @@ -14136,12 +14360,6 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, assert((E->getOpcode() == BO_Cmp || E->getType()->isIntegralOrEnumerationType()) && "unsupported binary expression evaluation"); - auto Error = [&](const Expr *E) { - Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr); - return false; - }; - - bool IsRelational = E->isRelationalOp() || E->getOpcode() == BO_Cmp; bool IsEquality = E->isEqualityOp(); QualType LHSTy = E->getLHS()->getType(); @@ -14156,10 +14374,10 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, if (!EvaluateInteger(E->getRHS(), RHS, Info) || !LHSOK) return false; if (LHS < RHS) - return Success(CmpResult::Less, E); + return Success(CmpResult::Less); if (LHS > RHS) - return Success(CmpResult::Greater, E); - return Success(CmpResult::Equal, E); + return Success(CmpResult::Greater); + return Success(CmpResult::Equal); } if (LHSTy->isFixedPointType() || RHSTy->isFixedPointType()) { @@ -14172,10 +14390,10 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, if (!EvaluateFixedPointOrInteger(E->getRHS(), RHSFX, Info) || !LHSOK) return false; if (LHSFX < RHSFX) - return Success(CmpResult::Less, E); + return Success(CmpResult::Less); if (LHSFX > RHSFX) - return Success(CmpResult::Greater, E); - return Success(CmpResult::Equal, E); + return Success(CmpResult::Greater); + return Success(CmpResult::Equal); } if (LHSTy->isAnyComplexType() || RHSTy->isAnyComplexType()) { @@ -14211,12 +14429,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, APFloat::cmpResult CR_i = LHS.getComplexFloatImag().compare(RHS.getComplexFloatImag()); bool IsEqual = CR_r == APFloat::cmpEqual && CR_i == APFloat::cmpEqual; - return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal, E); + return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal); } else { assert(IsEquality && "invalid complex comparison"); bool IsEqual = LHS.getComplexIntReal() == RHS.getComplexIntReal() && LHS.getComplexIntImag() == RHS.getComplexIntImag(); - return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal, E); + return Success(IsEqual ? CmpResult::Equal : CmpResult::Unequal); } } @@ -14253,7 +14471,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, } llvm_unreachable("Unrecognised APFloat::cmpResult enum"); }; - return Success(GetCmpRes(), E); + return Success(GetCmpRes()); } if (LHSTy->isPointerType() && RHSTy->isPointerType()) { @@ -14266,145 +14484,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, if (!EvaluatePointer(E->getRHS(), RHSValue, Info) || !LHSOK) return false; - // Reject differing bases from the normal codepath; we special-case - // comparisons to null. - if (!HasSameBase(LHSValue, RHSValue)) { - auto DiagComparison = [&] (unsigned DiagID, bool Reversed = false) { - std::string LHS = LHSValue.toString(Info.Ctx, E->getLHS()->getType()); - std::string RHS = RHSValue.toString(Info.Ctx, E->getRHS()->getType()); - Info.FFDiag(E, DiagID) - << (Reversed ? RHS : LHS) << (Reversed ? LHS : RHS); - return false; - }; - // Inequalities and subtractions between unrelated pointers have - // unspecified or undefined behavior. - if (!IsEquality) - return DiagComparison( - diag::note_constexpr_pointer_comparison_unspecified); - // A constant address may compare equal to the address of a symbol. - // The one exception is that address of an object cannot compare equal - // to a null pointer constant. - // TODO: Should we restrict this to actual null pointers, and exclude the - // case of zero cast to pointer type? - if ((!LHSValue.Base && !LHSValue.Offset.isZero()) || - (!RHSValue.Base && !RHSValue.Offset.isZero())) - return DiagComparison(diag::note_constexpr_pointer_constant_comparison, - !RHSValue.Base); - // C++2c [intro.object]/10: - // Two objects [...] may have the same address if [...] they are both - // potentially non-unique objects. - // C++2c [intro.object]/9: - // An object is potentially non-unique if it is a string literal object, - // the backing array of an initializer list, or a subobject thereof. - // - // This makes the comparison result unspecified, so it's not a constant - // expression. - // - // TODO: Do we need to handle the initializer list case here? - if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue)) - return DiagComparison(diag::note_constexpr_literal_comparison); - if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue)) - return DiagComparison(diag::note_constexpr_opaque_call_comparison, - !IsOpaqueConstantCall(LHSValue)); - // We can't tell whether weak symbols will end up pointing to the same - // object. - if (IsWeakLValue(LHSValue) || IsWeakLValue(RHSValue)) - return DiagComparison(diag::note_constexpr_pointer_weak_comparison, - !IsWeakLValue(LHSValue)); - // We can't compare the address of the start of one object with the - // past-the-end address of another object, per C++ DR1652. - if (LHSValue.Base && LHSValue.Offset.isZero() && - isOnePastTheEndOfCompleteObject(Info.Ctx, RHSValue)) - return DiagComparison(diag::note_constexpr_pointer_comparison_past_end, - true); - if (RHSValue.Base && RHSValue.Offset.isZero() && - isOnePastTheEndOfCompleteObject(Info.Ctx, LHSValue)) - return DiagComparison(diag::note_constexpr_pointer_comparison_past_end, - false); - // We can't tell whether an object is at the same address as another - // zero sized object. - if ((RHSValue.Base && isZeroSized(LHSValue)) || - (LHSValue.Base && isZeroSized(RHSValue))) - return DiagComparison( - diag::note_constexpr_pointer_comparison_zero_sized); - return Success(CmpResult::Unequal, E); - } - - const CharUnits &LHSOffset = LHSValue.getLValueOffset(); - const CharUnits &RHSOffset = RHSValue.getLValueOffset(); - - SubobjectDesignator &LHSDesignator = LHSValue.getLValueDesignator(); - SubobjectDesignator &RHSDesignator = RHSValue.getLValueDesignator(); - - // C++11 [expr.rel]p2: - // - If two pointers point to non-static data members of the same object, - // or to subobjects or array elements fo such members, recursively, the - // pointer to the later declared member compares greater provided the - // two members have the same access control and provided their class is - // not a union. - // [...] - // - Otherwise pointer comparisons are unspecified. - if (!LHSDesignator.Invalid && !RHSDesignator.Invalid && IsRelational) { - bool WasArrayIndex; - unsigned Mismatch = FindDesignatorMismatch( - getType(LHSValue.Base), LHSDesignator, RHSDesignator, WasArrayIndex); - // At the point where the designators diverge, the comparison has a - // specified value if: - // - we are comparing array indices - // - we are comparing fields of a union, or fields with the same access - // Otherwise, the result is unspecified and thus the comparison is not a - // constant expression. - if (!WasArrayIndex && Mismatch < LHSDesignator.Entries.size() && - Mismatch < RHSDesignator.Entries.size()) { - const FieldDecl *LF = getAsField(LHSDesignator.Entries[Mismatch]); - const FieldDecl *RF = getAsField(RHSDesignator.Entries[Mismatch]); - if (!LF && !RF) - Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_classes); - else if (!LF) - Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) - << getAsBaseClass(LHSDesignator.Entries[Mismatch]) - << RF->getParent() << RF; - else if (!RF) - Info.CCEDiag(E, diag::note_constexpr_pointer_comparison_base_field) - << getAsBaseClass(RHSDesignator.Entries[Mismatch]) - << LF->getParent() << LF; - else if (!LF->getParent()->isUnion() && - LF->getAccess() != RF->getAccess()) - Info.CCEDiag(E, - diag::note_constexpr_pointer_comparison_differing_access) - << LF << LF->getAccess() << RF << RF->getAccess() - << LF->getParent(); - } - } - - // The comparison here must be unsigned, and performed with the same - // width as the pointer. - unsigned PtrSize = Info.Ctx.getTypeSize(LHSTy); - uint64_t CompareLHS = LHSOffset.getQuantity(); - uint64_t CompareRHS = RHSOffset.getQuantity(); - assert(PtrSize <= 64 && "Unexpected pointer width"); - uint64_t Mask = ~0ULL >> (64 - PtrSize); - CompareLHS &= Mask; - CompareRHS &= Mask; - - // If there is a base and this is a relational operator, we can only - // compare pointers within the object in question; otherwise, the result - // depends on where the object is located in memory. - if (!LHSValue.Base.isNull() && IsRelational) { - QualType BaseTy = getType(LHSValue.Base); - if (BaseTy->isIncompleteType()) - return Error(E); - CharUnits Size = Info.Ctx.getTypeSizeInChars(BaseTy); - uint64_t OffsetLimit = Size.getQuantity(); - if (CompareLHS > OffsetLimit || CompareRHS > OffsetLimit) - return Error(E); + if (!EvaluateLValueComparison(Info, E, E->getOpcode(), LHSTy, LHSValue, + RHSTy, RHSValue, Success)) { + return false; } - if (CompareLHS < CompareRHS) - return Success(CmpResult::Less, E); - if (CompareLHS > CompareRHS) - return Success(CmpResult::Greater, E); - return Success(CmpResult::Equal, E); + return true; } if (LHSTy->isMemberPointerType()) { @@ -14438,7 +14523,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, // null, they compare unequal. if (!LHSValue.getDecl() || !RHSValue.getDecl()) { bool Equal = !LHSValue.getDecl() && !RHSValue.getDecl(); - return Success(Equal ? CmpResult::Equal : CmpResult::Unequal, E); + return Success(Equal ? CmpResult::Equal : CmpResult::Unequal); } // Otherwise if either is a pointer to a virtual member function, the @@ -14455,7 +14540,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, // they were dereferenced with a hypothetical object of the associated // class type. bool Equal = LHSValue == RHSValue; - return Success(Equal ? CmpResult::Equal : CmpResult::Unequal, E); + return Success(Equal ? CmpResult::Equal : CmpResult::Unequal); } if (LHSTy->isNullPtrType()) { @@ -14468,7 +14553,7 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, if (!EvaluatePointer(E->getLHS(), Res, Info) || !EvaluatePointer(E->getRHS(), Res, Info)) return false; - return Success(CmpResult::Equal, E); + return Success(CmpResult::Equal); } return DoAfter(); @@ -14478,7 +14563,7 @@ bool RecordExprEvaluator::VisitBinCmp(const BinaryOperator *E) { if (!CheckLiteralType(Info, E)) return false; - auto OnSuccess = [&](CmpResult CR, const BinaryOperator *E) { + auto OnSuccess = [&](CmpResult CR) { ComparisonCategoryResult CCR; switch (CR) { case CmpResult::Unequal: @@ -14538,7 +14623,7 @@ bool IntExprEvaluator::VisitBinaryOperator(const BinaryOperator *E) { if (E->isComparisonOp()) { // Evaluate builtin binary comparisons by evaluating them as three-way // comparisons and then translating the result. - auto OnSuccess = [&](CmpResult CR, const BinaryOperator *E) { + auto OnSuccess = [&](CmpResult CR) { assert((CR != CmpResult::Unequal || E->isEqualityOp()) && "should only produce Unequal for equality comparisons"); bool IsEqual = CR == CmpResult::Equal, @@ -16268,6 +16353,25 @@ class VoidExprEvaluator } } + bool VisitAtomicExpr(const AtomicExpr *E) { + if (!EvaluateAtomicOrder(E, Info)) + return false; + + switch (E->getOp()) { + default: + return Error(E); + case AtomicExpr::AO__atomic_load: + return EvaluateAtomicLoadInto(E, Info); + case AtomicExpr::AO__atomic_exchange: + return EvaluateAtomicExchangeInto(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 EvaluateAtomicStore(E, Info); + } + } + bool VisitCallExpr(const CallExpr *E) { if (!IsConstantEvaluatedBuiltinCall(E)) return ExprEvaluatorBaseTy::VisitCallExpr(E); @@ -16281,6 +16385,14 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); + case Builtin::BI__c11_atomic_thread_fence: + case Builtin::BI__c11_atomic_signal_fence: + case Builtin::BI__atomic_thread_fence: + case Builtin::BI__atomic_signal_fence: { + APSInt IgnoredAtomicOrdering; + return EvaluateInteger(E->getArg(0), IgnoredAtomicOrdering, Info); + } + default: return false; } @@ -17893,4 +18005,453 @@ std::optional EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, IsWithinLifetimeHandler handler{Info}; return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); } + } // namespace + +static bool EvaluateAtomicOrder(const AtomicExpr *E, EvalInfo &Info) { + // We need to evaluate Order argument(s), but we ignore it as constant + // evaluation is single threaded. + APSInt AtomicOrderValue; + + if (E->getOp() != AtomicExpr::AO__c11_atomic_init) { + const Expr *OrderSuccess = E->getOrder(); + if (OrderSuccess && !EvaluateInteger(OrderSuccess, AtomicOrderValue, Info)) + return false; + + // FIXME: this diagnostics is emited twice, once in SemaChecking.cpp and + // once here + if (!AtomicExpr::isValidOrderingForOp(AtomicOrderValue.getSExtValue(), + E->getOp())) { + Info.FFDiag(E->getOrder(), diag::note_atomic_op_with_invalid_memory_order) + << /*success=*/(E->isCmpXChg()) << E->getOrder()->getExprLoc(); + return false; + } + } + + if (E->isCmpXChg()) { + const Expr *OrderFail = E->getOrderFail(); + if (!EvaluateInteger(OrderFail, AtomicOrderValue, Info)) + return false; + + if (!AtomicExpr::isValidOrderingForOp(AtomicOrderValue.getSExtValue(), + E->getOp())) { + Info.FFDiag(E->getOrderFail(), + diag::note_atomic_op_with_invalid_memory_order) + << 2 << E->getOrderFail()->getExprLoc(); + return false; + } + } + + return true; +} + +static bool EvaluateAtomicLoad(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 EvaluateAtomicLoadInto(const AtomicExpr *E, EvalInfo &Info) { + APValue LocalResult; + + if (!EvaluateAtomicLoad(E, LocalResult, Info)) + return false; + + assert(E->getVal1()->getType()->isPointerType()); + QualType PointeeTy = E->getVal1()->getType()->getPointeeType(); + LValue PointeeLV; + + if (!EvaluatePointer(E->getVal1(), PointeeLV, Info)) + return false; + + if (!handleAssignment(Info, E->getVal1(), PointeeLV, PointeeTy, LocalResult)) + return false; + + return true; +} + +static bool EvaluateAtomicStore(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 EvaluateAtomicExchange(const AtomicExpr *E, APValue &Result, + EvalInfo &Info) { + assert(E->getOp() == AtomicExpr::AO__c11_atomic_exchange || + E->getOp() == AtomicExpr::AO__atomic_exchange_n); + + if (!EvaluateAtomicLoad(E, Result, Info)) + return false; + + if (!EvaluateAtomicStore(E, Info)) + return false; + + return true; +} + +static bool EvaluateAtomicExchangeInto(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 value pointer by argument of the exchange operation. + 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. + assert(E->getVal2()->getType()->isPointerType()); + QualType PointeeTy = E->getVal2()->getType()->getPointeeType(); + LValue PointeeLV; + + if (!EvaluatePointer(E->getVal2(), PointeeLV, Info)) + return false; + + if (!handleAssignment(Info, E->getVal2(), PointeeLV, PointeeTy, + PreviousValue)) + return false; + + return true; +} + +static bool EvaluateAtomicFetchOp(const AtomicExpr *E, APValue &Result, + EvalInfo &Info, bool StoreToResultAfter) { + // Read the 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()) { + assert(CurrentValue.isInt()); + assert(ArgumentVal.isInt()); + + 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: + // Atomic operations are defined for overflow + 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(std::max(AtomicInt, ArgumentInt)); + break; + case AtomicExpr::AO__c11_atomic_fetch_min: + case AtomicExpr::AO__atomic_fetch_min: + case AtomicExpr::AO__atomic_min_fetch: + Replacement = APValue(std::min(AtomicInt, ArgumentInt)); + break; + default: + return false; + } + } else if (AtomicValueTy->isRealFloatingType()) { + assert(CurrentValue.isFloat()); + assert(ArgumentVal.isFloat()); + + 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: + 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: + // GCC's atomic fetch-op doesn't support float operands. + return false; + } + + if (!checkFloatingPointResult(Info, E, St)) + return false; + + } else if (AtomicValueTy->isPointerType()) { + assert(CurrentValue.isLValue()); + assert(ArgumentVal.isInt()); + + LValue AtomicPtr; + AtomicPtr.setFrom(Info.Ctx, CurrentValue); + + APSInt ArgumentInt = ArgumentVal.getInt(); + + auto PointeeTy = AtomicValueTy->getPointeeType(); + + if (PointeeTy->isVoidType()) { + // Resolve type from LValue Designator, so we can do safely arithmetic + // over 'void *' with GCC atomic builtins. + PointeeTy = AtomicPtr.Designator.getType(Info.Ctx); + } else if (PointeeTy->isIncompleteType()) { + Info.FFDiag(E, diag::err_typecheck_arithmetic_incomplete_or_sizeless_type) + << 0 << PointeeTy; + return false; + } + + // Calculate size of pointee object. + CharUnits SizeOfPointee; + if (!HandleSizeof(Info, E->getExprLoc(), PointeeTy, SizeOfPointee)) + return false; + + // GCC's atomic_fetch add/sub operations takes arguments in bytes and + // not in multiples of 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/decrementing pointer by size which is not dividable by + // pointee size is not allowed. + // FIXME: check if we need to also look for alignof(T) + if ((ArgumentInt % SizeOfOneItem) != 0) { + Info.FFDiag(E->getBuiltinLoc(), diag::note_unaligned_atomic_pointer_op) + << E->getBuiltinLoc() + << E->getVal1()->getSourceRange() // Problematic argument. + << ArgumentInt << PointeeTy << SizeOfOneItem; + 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 { + assert(false && "only float, int, or pointer supported"); + 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 EvaluateAtomicCompareExchange(const AtomicExpr *E, APValue &Result, + EvalInfo &Info) { + if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange_n || + E->getOp() == AtomicExpr::AO__atomic_compare_exchange) { + + APSInt WeakIgnoredResult; + + const Expr *Weak = E->getWeak(); + if (!EvaluateInteger(Weak, WeakIgnoredResult, Info)) + return false; + } + + // Dereference _Atomic T * (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; + + // Do the comparison for equality. + 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()) { + // Do the comparison of pointers. + LValue LHS, RHS; + LHS.setFrom(Info.Ctx, AtomicVal); + RHS.setFrom(Info.Ctx, ExpectedVal); + + auto OnSuccess = [&](CmpResult CR) { + DoExchange = (CR == CmpResult::Equal); + return true; + }; + + if (!EvaluateLValueComparison(Info, E, BO_EQ, AtomicTy, LHS, ExpectedTy, + RHS, OnSuccess)) { + return false; + } + + } else { + return false; + } + + APValue Replacement; + if (E->getOp() == AtomicExpr::AO__atomic_compare_exchange) { + // GCC atomic_compare_exchange takes pointer to a value and not value + // itself. + 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; + } + + // Return previous value to the Expected pointer. + if (!handleAssignment(Info, E, ExpectedLV, ExpectedTy, AtomicVal)) + return false; + + // Return boolean result if operation was successful. + Result = APValue(Info.Ctx.MakeIntValue(DoExchange, E->getType())); + return true; +} diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 28dcfaac2e84f..e485c6496858a 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -3607,7 +3607,8 @@ bool Sema::CheckOtherCall(CallExpr *TheCall, const FunctionProtoType *Proto) { return false; } -static bool isValidOrderingForOp(int64_t Ordering, AtomicExpr::AtomicOp Op) { +bool AtomicExpr::isValidOrderingForOp(int64_t Ordering, + AtomicExpr::AtomicOp Op) { if (!llvm::isValidAtomicOrderingCABI(Ordering)) return false; @@ -4164,7 +4165,8 @@ ExprResult Sema::BuildAtomicExpr(SourceRange CallRange, SourceRange ExprRange, if (SubExprs.size() >= 2 && Form != Init) { std::optional Success = SubExprs[1]->getIntegerConstantExpr(Context); - if (Success && !isValidOrderingForOp(Success->getSExtValue(), Op)) { + if (Success && + !AtomicExpr::isValidOrderingForOp(Success->getSExtValue(), Op)) { Diag(SubExprs[1]->getBeginLoc(), diag::warn_atomic_op_has_invalid_memory_order) << /*success=*/(Form == C11CmpXchg || Form == GNUCmpXchg) diff --git a/clang/test/Sema/atomic-constexpr-c11-builtins.cpp b/clang/test/Sema/atomic-constexpr-c11-builtins.cpp new file mode 100644 index 0000000000000..9d1a9d5461c30 --- /dev/null +++ b/clang/test/Sema/atomic-constexpr-c11-builtins.cpp @@ -0,0 +1,330 @@ +// RUN: %clang_cc1 -std=c++2c %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 struct identity { + using type = T; +}; + +template using do_not_deduce = typename identity::type; + +// -- SIGNAL and THREAD fence -- +consteval int fence_test(int v) { + // both are no-op in constexpr + __c11_atomic_thread_fence(__ATOMIC_ACQUIRE); + __c11_atomic_signal_fence(__ATOMIC_ACQUIRE); + return v; +} + +static_assert(fence_test(42) == 42); + +// -- LOAD -- + +template consteval T init(T value) { + auto * av = new _Atomic T; + __c11_atomic_init(av, value); + auto result = __c11_atomic_load(av, __ATOMIC_RELAXED); + delete av; + return result; +} + +// integers +static_assert(init(true) == true); +static_assert(init(false) == false); + +static_assert(init(42) == 42); +static_assert(init(-128) == -128); + +static_assert(init(42u) == 42u); +static_assert(init(0xFFFFFFFFu) == 0xFFFFFFFFu); + +// floats +static_assert(init(42.3) == 42.3); +static_assert(init(42.3f) == 42.3f); + +// pointers +static_assert(init(&array[0]) == &array[0]); +static_assert(init(&small_array[1]) == &small_array[1]); + +// -- LOAD -- + +template 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 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 struct two_values { + T before; + T after; + constexpr friend bool operator==(two_values, two_values) = default; +}; + +template consteval auto exchange(T value, do_not_deduce replacement) -> two_values { + _Atomic(T) av = T{value}; + T previous = __c11_atomic_exchange(&av, replacement, __ATOMIC_RELAXED); + return two_values{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 struct comp_result { + bool success; + T output; + T after; + + constexpr comp_result(bool success_, T output_, do_not_deduce after_): success{success_}, output{output_}, after{after_} { } + + constexpr friend bool operator==(comp_result, comp_result) = default; +}; + +template constexpr auto comp_success(T output, do_not_deduce after) { + return comp_result{true, output, after}; +} + +template constexpr auto comp_failure(T after) { + return comp_result{false, after, after}; +} + +template consteval auto compare_exchange_weak(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + _Atomic(T) av = T{original}; + const bool success = __c11_atomic_compare_exchange_weak(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto compare_exchange_strong(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + _Atomic(T) av = T{original}; + const bool success = __c11_atomic_compare_exchange_strong(&av, &expected, replacement, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto fetch_add(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values{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}); + +// __int128 +static_assert(fetch_add(__int128{42}, __int128{10}) == two_values{__int128{42}, __int128{52}}); +static_assert(fetch_add(__int128{int_max}, __int128{1}) == two_values{__int128{int_max}, __int128{int_max} + __int128{1}}); // it's much bigger than 64bit + +// 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 consteval auto fetch_sub(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values{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 consteval auto fetch_and(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_and(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_or(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_or(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_xor(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_nand(T original, Y arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED); + return two_values{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 consteval auto fetch_min(T original, T arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_min(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __c11_atomic_load(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_max(T original, T arg) -> two_values { + _Atomic(T) av = T{original}; + const T result = __c11_atomic_fetch_max(&av, arg, __ATOMIC_RELAXED); + return two_values{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/Sema/atomic-constexpr-gcc-builtins-diagnostics.cpp b/clang/test/Sema/atomic-constexpr-gcc-builtins-diagnostics.cpp new file mode 100644 index 0000000000000..572340c073237 --- /dev/null +++ b/clang/test/Sema/atomic-constexpr-gcc-builtins-diagnostics.cpp @@ -0,0 +1,122 @@ +// RUN: %clang_cc1 -std=c++2c -verify %s + +// _fetch_add + +constexpr bool test_int_fetch_add(int arg) { + int array[8] = {1,2,3,4,5,6,7,8}; + int * ptr = &array[0]; + int * old = __atomic_fetch_add(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 5 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + return true; +} + +static_assert(test_int_fetch_add(5)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_fetch_add(5)'}} + +static_assert(test_int_fetch_add(sizeof(int))); + +constexpr bool test_long_long_fetch_add(int arg) { + long long array[8] = {1,2,3,4,5,6,7,8}; + long long * ptr = &array[0]; + long long * old = __atomic_fetch_add(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 6 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}} + return true; +} + +static_assert(test_long_long_fetch_add(6)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_fetch_add(6)'}} + + +static_assert(test_long_long_fetch_add(sizeof(long long) * 4)); + +// _fetch_sub + +constexpr bool test_int_fetch_sub(int arg) { + int array[8] = {1,2,3,4,5,6,7,8}; + int * ptr = &array[7]; + int * old = __atomic_fetch_sub(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 7 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + return true; +} + +static_assert(test_int_fetch_sub(7)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_fetch_sub(7)'}} + +static_assert(test_int_fetch_sub(sizeof(int))); + +constexpr bool test_long_long_fetch_sub(int arg) { + long long array[8] = {1,2,3,4,5,6,7,8}; + long long * ptr = &array[7]; + long long * old = __atomic_fetch_sub(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 11 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}} + return true; +} + +static_assert(test_long_long_fetch_sub(11)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_fetch_sub(11)'}} + + +static_assert(test_long_long_fetch_sub(sizeof(long long) * 4)); + + +// _add_fetch + +constexpr bool test_int_add_fetch(int arg) { + int array[8] = {1,2,3,4,5,6,7,8}; + int * ptr = &array[0]; + int * old = __atomic_add_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 5 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + return true; +} + +static_assert(test_int_add_fetch(5)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_add_fetch(5)'}} + +static_assert(test_int_add_fetch(sizeof(int))); + +constexpr bool test_long_long_add_fetch(int arg) { + long long array[8] = {1,2,3,4,5,6,7,8}; + long long * ptr = &array[0]; + long long * old = __atomic_add_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 6 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}} + return true; +} + +static_assert(test_long_long_add_fetch(6)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_add_fetch(6)'}} + + +static_assert(test_long_long_add_fetch(sizeof(long long) * 4)); + +// _sub_fetch + +constexpr bool test_int_sub_fetch(int arg) { + int array[8] = {1,2,3,4,5,6,7,8}; + int * ptr = &array[7]; + int * old = __atomic_sub_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 7 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + return true; +} + +static_assert(test_int_sub_fetch(7)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_int_sub_fetch(7)'}} + +static_assert(test_int_sub_fetch(sizeof(int))); + +constexpr bool test_long_long_sub_fetch(int arg) { + long long array[8] = {1,2,3,4,5,6,7,8}; + long long * ptr = &array[7]; + long long * old = __atomic_sub_fetch(&ptr, arg, __ATOMIC_RELAXED); // expected-note-re {{atomic pointer operation with argument 11 not aligned to size of pointee type (sizeof 'long long' is {{[0-9]+}})}} + return true; +} + +static_assert(test_long_long_sub_fetch(11)); // expected-error {{static assertion expression is not an integral constant expression}} expected-note {{in call to 'test_long_long_sub_fetch(11)'}} + + +static_assert(test_long_long_sub_fetch(sizeof(long long) * 4)); + + +// behave nicely with cosntexpr variables +constexpr int val = 42; + +// we can't modify other constexpr variable +constexpr int oldval = __atomic_exchange_n(const_cast(&val), 10, __ATOMIC_RELAXED); // #exchange_n +// expected-error@#exchange_n {{constexpr variable 'oldval' must be initialized by a constant expression}} +// expected-note@#exchange_n {{a constant expression cannot modify an object that is visible outside that expression}} + +// we can load from constexpr variable +constexpr int cvar = 42; +static_assert(__atomic_load_n(&cvar, __ATOMIC_RELAXED) == 42); + + +// we can't store into constexpr variable +constexpr int out = 10; +constexpr int tmp = (__atomic_load(&cvar, const_cast(&val), __ATOMIC_RELAXED), out); // #load +// expected-error@#load {{constexpr variable 'tmp' must be initialized by a constant expression}} +// expected-note@#load {{a constant expression cannot modify an object that is visible outside that expression}} diff --git a/clang/test/Sema/atomic-constexpr-gcc-builtins-with-void.cpp b/clang/test/Sema/atomic-constexpr-gcc-builtins-with-void.cpp new file mode 100644 index 0000000000000..37308d74f0516 --- /dev/null +++ b/clang/test/Sema/atomic-constexpr-gcc-builtins-with-void.cpp @@ -0,0 +1,134 @@ +// RUN: %clang_cc1 -std=c++2c -verify %s + +template constexpr auto test(int off) { + T array[] = {T{1}, T{2}, T{3}, T{4}, T{5}}; + Y * ptr = static_cast(&array[2]); + Y * nptr = __atomic_add_fetch(&ptr, off, __ATOMIC_RELAXED); // #misalign + return *static_cast(nptr); +} + +constexpr auto tmp0 = test(0); +static_assert(tmp0 == 3); + +constexpr auto tmpmsz = test(-sizeof(int)); +static_assert(tmpmsz == 2); + +constexpr auto tmp1 = test(1); // #tmp1 +// expected-error-re@#tmp1 {{constexpr variable '{{tmp[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#tmp1 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 1 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto tmp2 = test(2); // #tmp2 +// expected-error-re@#tmp2 {{constexpr variable '{{tmp[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#tmp2 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 2 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto tmp3 = test(3); // #tmp3 +// expected-error-re@#tmp3 {{constexpr variable '{{tmp[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#tmp3 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 3 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto tmp4 = test(sizeof(int)); +static_assert(tmp4 == 4); + +// ================= +// GCC atomics work with void * pointers too, we must make sure they are aligned for size properly +constexpr auto void0 = test(0); +static_assert(void0 == 3); + +// negative +constexpr auto voidmsz = test(-sizeof(int)); +static_assert(voidmsz == 2); + +constexpr auto void1 = test(1); // #void1 +// expected-error-re@#void1 {{constexpr variable '{{void[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#void1 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 1 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto void2 = test(2); // #void2 +// expected-error-re@#void2 {{constexpr variable '{{void[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#void2 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 2 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto void3 = test(3); // #void3 +// expected-error-re@#void3 {{constexpr variable '{{void[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#void3 {{in call to '{{.+}}'}} +// expected-note-re@#misalign {{atomic pointer operation with argument 3 not aligned to size of pointee type (sizeof 'int' is {{[0-9]+}})}} + +constexpr auto void4 = test(sizeof(int)); +static_assert(void4 == 4); + +// ================= +// check overaligned types works too + +struct alignas(16) special { + int value; +}; + +static_assert(sizeof(special) == 16); + +// negative +constexpr auto specm16 = test(-16); +static_assert(specm16.value == 2); + +// no change +constexpr auto spec0 = test(0); +static_assert(spec0.value == 3); + +constexpr auto spec1 = test(1); // #spec1 +// expected-error-re@#spec1 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec1 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 1 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec2 = test(2); // #spec2 +// expected-error-re@#spec2 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec2 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 2 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec3 = test(3); // #spec3 +// expected-error-re@#spec3 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec3 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 3 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec4 = test(4); // #spec4 +// expected-error-re@#spec4 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec4 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 4 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec5 = test(5); // #spec5 +// expected-error-re@#spec5 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec5 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 5 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec8 = test(8); // #spec8 +// expected-error-re@#spec8 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec8 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 8 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto spec15 = test(15); // #spec15 +// expected-error-re@#spec15 {{constexpr variable '{{spec[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#spec15 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument 15 not aligned to size of pointee type (sizeof 'special' is 16)}} + +constexpr auto specm15 = test(-15); // #specm15 +// expected-error-re@#specm15 {{constexpr variable '{{specm[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#specm15 {{in call to '{{.+}}'}} +// expected-note@#misalign {{atomic pointer operation with argument -15 not aligned to size of pointee type (sizeof 'special' is 16)}} + +// =============== +// any operations on incomplete types are disallowed +// (with exception of void* pointer pointing to complete type) + +struct incomplete; + +constexpr void * test2(void * ptr, int off) { + incomplete * inc_ptr = static_cast(ptr); + incomplete * nptr = __atomic_add_fetch(&inc_ptr, off, __ATOMIC_RELAXED); // #incomplete + return static_cast(nptr); +} + +constexpr void * incmp0 = test2(nullptr, 0); // #incmp0 + +// expected-error-re@#incmp0 {{constexpr variable '{{incmp[0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#incmp0 {{in call to '{{.+}}'}} +// expected-error@#incomplete {{arithmetic on a pointer to an incomplete type 'incomplete'}} diff --git a/clang/test/Sema/atomic-constexpr-gcc-builtins.cpp b/clang/test/Sema/atomic-constexpr-gcc-builtins.cpp new file mode 100644 index 0000000000000..425b0a9a71a1e --- /dev/null +++ b/clang/test/Sema/atomic-constexpr-gcc-builtins.cpp @@ -0,0 +1,507 @@ +// RUN: %clang_cc1 -std=c++2c %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 struct identity { + using type = T; +}; + +template using do_not_deduce = typename identity::type; + +// -- SIGNAL and THREAD fence -- +consteval int fence_test(int v) { + // both are no-op in constexpr + __atomic_thread_fence(__ATOMIC_ACQUIRE); + __atomic_signal_fence(__ATOMIC_ACQUIRE); + return v; +} + +static_assert(fence_test(42) == 42); + +// -- LOAD -- + +template 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 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 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 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 struct two_values { + T before; + T after; + constexpr friend bool operator==(two_values, two_values) = default; +}; + +template consteval auto exchange(T value, do_not_deduce replacement) -> two_values { + T av = T{value}; + T out{}; + __atomic_exchange(&av, &replacement, &out, __ATOMIC_RELAXED); + return two_values{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 consteval auto exchange_n(T value, do_not_deduce replacement) -> two_values { + T av = T{value}; + T previous = __atomic_exchange_n(&av, replacement, __ATOMIC_RELAXED); + return two_values{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 struct comp_result { + bool success; + T output; + T after; + + constexpr comp_result(bool success_, T output_, do_not_deduce after_): success{success_}, output{output_}, after{after_} { } + + constexpr friend bool operator==(comp_result, comp_result) = default; +}; + +template constexpr auto comp_success(T output, do_not_deduce after) { + return comp_result{true, output, after}; +} + +template constexpr auto comp_failure(T after) { + return comp_result{false, after, after}; +} + +template consteval auto compare_exchange_weak(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + T av = T{original}; + const bool success = __atomic_compare_exchange(&av, &expected, &replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto compare_exchange_strong(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + T av = T{original}; + const bool success = __atomic_compare_exchange(&av, &expected, &replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto compare_exchange_weak_n(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + T av = T{original}; + const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto compare_exchange_strong_n(T original, do_not_deduce expected, do_not_deduce replacement) -> comp_result { + T av = T{original}; + const bool success = __atomic_compare_exchange_n(&av, &expected, replacement, true, __ATOMIC_RELAXED, __ATOMIC_RELAXED); + return comp_result{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 consteval auto fetch_add(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_add_ptr(T original, Y arg) -> two_values { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast(nullptr)); + arg *= pointee_size; + const T result = __atomic_fetch_add(&av, arg, __ATOMIC_RELAXED); + return two_values{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}); + +// __int128 +static_assert(fetch_add(__int128{42}, __int128{10}) == two_values{__int128{42}, __int128{52}}); +static_assert(fetch_add(__int128{int_max}, __int128{1}) == two_values{__int128{int_max}, __int128{int_max} + __int128{1}}); // it's much bigger than 64bit + +// 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 consteval auto fetch_sub(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_sub_ptr(T original, Y arg) -> two_values { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast(nullptr)); + arg *= pointee_size; + const T result = __atomic_fetch_sub(&av, arg, __ATOMIC_RELAXED); + return two_values{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 consteval auto fetch_and(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_and(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_or(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_or(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_xor(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_xor(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_nand(T original, Y arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_nand(&av, arg, __ATOMIC_RELAXED); + return two_values{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 consteval auto fetch_min(T original, T arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_min(&av, arg, __ATOMIC_RELAXED); + return two_values{result, __atomic_load_n(&av, __ATOMIC_RELAXED)}; +} + +template consteval auto fetch_max(T original, T arg) -> two_values { + T av = T{original}; + const T result = __atomic_fetch_max(&av, arg, __ATOMIC_RELAXED); + return two_values{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 consteval auto add_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_add_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template consteval auto add_fetch_ptr(T original, Y arg) -> T { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast(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 consteval auto sub_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_sub_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template consteval auto sub_fetch_ptr(T original, Y arg) -> T { + T av = T{original}; + constexpr auto pointee_size = sizeof(*static_cast(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 consteval auto and_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_and_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template consteval auto or_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_or_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template consteval auto xor_fetch(T original, Y arg) -> T { + T av = T{original}; + return __atomic_xor_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template 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 consteval auto min_fetch(T original, T arg) -> T { + T av = T{original}; + return __atomic_min_fetch(&av, arg, __ATOMIC_RELAXED); +} + +template 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); + + diff --git a/clang/test/Sema/atomic-constexpr-order.cpp b/clang/test/Sema/atomic-constexpr-order.cpp new file mode 100644 index 0000000000000..9e6905aad61f8 --- /dev/null +++ b/clang/test/Sema/atomic-constexpr-order.cpp @@ -0,0 +1,80 @@ +// RUN: %clang_cc1 -std=c++2c -verify %s + +// we can't use unknown ordering values +namespace load { + +constexpr int constant = 42; +constexpr int both = __atomic_load_n(&constant, 111111); // #constant +// expected-note@#constant {{evaluated memory order argument to atomic operation is not allowed}} +// expected-warning@#constant {{memory order argument to atomic operation is invalid}} +// expected-error@#constant {{constexpr variable 'both' must be initialized by a constant expression}} + +constexpr int load_with_order(int order) { + int val = 42; + return __atomic_load_n(&val, order); // #load_n + // expected-note@#load_n 1+ {{evaluated memory order argument to atomic operation is not allowed}} +} + +// unknown values are not allowed +constexpr int tmp1 = load_with_order(111111); // #load-order-fail1 +// expected-error-re@#load-order-fail1 {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#load-order-fail1 {{in call to 'load_with_order({{[0-9]+}})'}} +constexpr int tmp2 = load_with_order(256); // #load-order-fail2 +// expected-error-re@#load-order-fail2 {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#load-order-fail2 {{in call to 'load_with_order({{[0-9]+}})'}} + +// load ordering allowed +constexpr int tmp3 = load_with_order(__ATOMIC_RELAXED); // fine +constexpr int tmp4 = load_with_order(__ATOMIC_CONSUME); // fine +constexpr int tmp5 = load_with_order(__ATOMIC_ACQUIRE); // fine +constexpr int tmp6 = load_with_order(__ATOMIC_SEQ_CST); // fine + +// RELEASE or ACQ_REL are not allowed for load +constexpr int tmp7 = load_with_order(__ATOMIC_RELEASE); // #load-release-not-allowed +constexpr int tmp8 = load_with_order(__ATOMIC_ACQ_REL); // #load-acq-rel-not-allowed + +// expected-error-re@#load-release-not-allowed {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#load-release-not-allowed {{in call to 'load_with_order({{[0-9]+}})'}} + +// expected-error-re@#load-acq-rel-not-allowed {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#load-acq-rel-not-allowed {{in call to 'load_with_order({{[0-9]+}})'}} + +} + +namespace store { + +constexpr bool store_with_order(int order) { + int value = 0; + __atomic_store_n(&value, 42, order); // #store + // expected-note@#store 1+ {{evaluated memory order argument to atomic operation is not allowed}} + return true; +} + +// unknown values are not allowed +constexpr auto tmp1 = store_with_order(111111); // #store-order-fail1 +// expected-error-re@#store-order-fail1 {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#store-order-fail1 {{in call to 'store_with_order({{[0-9]+}})'}} +constexpr auto tmp2 = store_with_order(256); // #store-order-fail2 +// expected-error-re@#store-order-fail2 {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#store-order-fail2 {{in call to 'store_with_order({{[0-9]+}})'}} + +constexpr int tmp3 = store_with_order(__ATOMIC_RELAXED); // fine +constexpr int tmp4 = store_with_order(__ATOMIC_CONSUME); // #store-consume-not-allowed +constexpr int tmp5 = store_with_order(__ATOMIC_ACQUIRE); // #store-acquire-not-allowed +constexpr int tmp6 = store_with_order(__ATOMIC_SEQ_CST); // fine +constexpr int tmp7 = store_with_order(__ATOMIC_RELEASE); // fine +constexpr int tmp8 = store_with_order(__ATOMIC_ACQ_REL); // #store-acq-rel-not-allowed + +// expected-error-re@#store-consume-not-allowed {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#store-consume-not-allowed {{in call to 'store_with_order({{[0-9]+}})'}} + +// expected-error-re@#store-acquire-not-allowed {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#store-acquire-not-allowed {{in call to 'store_with_order({{[0-9]+}})'}} + +// expected-error-re@#store-acq-rel-not-allowed {{constexpr variable '{{[a-z0-9]+}}' must be initialized by a constant expression}} +// expected-note-re@#store-acq-rel-not-allowed {{in call to 'store_with_order({{[0-9]+}})'}} + + + +} +