Author: Doug Wyatt Date: 2024-06-24T12:51:31+02:00 New Revision: f03cb005eb4ba3c6fb645aca2228e907db8cd452
URL: https://github.com/llvm/llvm-project/commit/f03cb005eb4ba3c6fb645aca2228e907db8cd452 DIFF: https://github.com/llvm/llvm-project/commit/f03cb005eb4ba3c6fb645aca2228e907db8cd452.diff LOG: [Clang] Introduce `nonblocking`/`nonallocating` attributes (#84983) Introduce `nonblocking` and `nonallocating` attributes. RFC is here: https://discourse.llvm.org/t/rfc-nolock-and-noalloc-attributes/76837 This PR introduces the attributes, with some changes in Sema to deal with them as extensions to function (proto)types. There are some basic type checks, most importantly, a warning when trying to spoof the attribute (implicitly convert a function without the attribute to one that has it). A second, follow-on pull request will introduce new caller/callee verification. --------- Co-authored-by: Doug Wyatt <dwy...@apple.com> Co-authored-by: Shafik Yaghmour <shafik.yaghm...@intel.com> Co-authored-by: Aaron Ballman <aa...@aaronballman.com> Co-authored-by: Sirraide <aeternalm...@gmail.com> Added: clang/test/Sema/attr-nonblocking-sema.c clang/test/Sema/attr-nonblocking-sema.cpp clang/test/Sema/attr-nonblocking-syntax.cpp Modified: clang/docs/ReleaseNotes.rst clang/include/clang/AST/AbstractBasicReader.h clang/include/clang/AST/AbstractBasicWriter.h clang/include/clang/AST/Decl.h clang/include/clang/AST/PropertiesBase.td clang/include/clang/AST/Type.h clang/include/clang/AST/TypeProperties.td clang/include/clang/Basic/Attr.td clang/include/clang/Basic/AttrDocs.td clang/include/clang/Basic/DiagnosticGroups.td clang/include/clang/Basic/DiagnosticSemaKinds.td clang/include/clang/Sema/Sema.h clang/lib/AST/ASTContext.cpp clang/lib/AST/Type.cpp clang/lib/AST/TypePrinter.cpp clang/lib/Sema/Sema.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaOverload.cpp clang/lib/Sema/SemaType.cpp clang/lib/Sema/TreeTransform.h Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 9c8f8c4a4fbaf..c6788d0deefae 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -511,6 +511,11 @@ Attribute Changes in Clang }; +- Introduced new function type attributes ``[[clang::nonblocking]]``, ``[[clang::nonallocating]]``, + ``[[clang::blocking]]``, and ``[[clang::allocating]]``, with GNU-style variants as well. + The attributes declare constraints about a function's behavior pertaining to blocking and + heap memory allocation. + Improvements to Clang's diagnostics ----------------------------------- - Clang now applies syntax highlighting to the code snippets it diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h index ab036f1d445ac..4b627c65e276b 100644 --- a/clang/include/clang/AST/AbstractBasicReader.h +++ b/clang/include/clang/AST/AbstractBasicReader.h @@ -244,6 +244,15 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> { return FunctionProtoType::ExtParameterInfo::getFromOpaqueValue(value); } + FunctionEffect readFunctionEffect() { + uint32_t value = asImpl().readUInt32(); + return FunctionEffect::fromOpaqueInt32(value); + } + + EffectConditionExpr readEffectConditionExpr() { + return EffectConditionExpr{asImpl().readExprRef()}; + } + NestedNameSpecifier *readNestedNameSpecifier() { auto &ctx = getASTContext(); diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h index 8e42fcaad1d38..b941add8bde88 100644 --- a/clang/include/clang/AST/AbstractBasicWriter.h +++ b/clang/include/clang/AST/AbstractBasicWriter.h @@ -222,6 +222,14 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> { asImpl().writeUInt32(epi.getOpaqueValue()); } + void writeFunctionEffect(FunctionEffect E) { + asImpl().writeUInt32(E.toOpaqueInt32()); + } + + void writeEffectConditionExpr(EffectConditionExpr CE) { + asImpl().writeExprRef(CE.getCondition()); + } + void writeNestedNameSpecifier(NestedNameSpecifier *NNS) { // Nested name specifiers usually aren't too long. I think that 8 would // typically accommodate the vast majority. diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 7fd80b90d1033..5957f14098363 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -3042,6 +3042,16 @@ class FunctionDecl : public DeclaratorDecl, /// computed and stored. unsigned getODRHash() const; + FunctionEffectsRef getFunctionEffects() const { + // Effects may diff er between declarations, but they should be propagated + // from old to new on any redeclaration, so it suffices to look at + // getMostRecentDecl(). + if (const auto *FPT = + getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) + return FPT->getFunctionEffects(); + return {}; + } + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { @@ -4670,6 +4680,13 @@ class BlockDecl : public Decl, public DeclContext { SourceRange getSourceRange() const override LLVM_READONLY; + FunctionEffectsRef getFunctionEffects() const { + if (const TypeSourceInfo *TSI = getSignatureAsWritten()) + if (const auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) + return FPT->getFunctionEffects(); + return {}; + } + // Implement isa/cast/dyncast/etc. static bool classof(const Decl *D) { return classofKind(D->getKind()); } static bool classofKind(Kind K) { return K == Block; } diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td index 6df1d93a7ba2e..5f7d619518762 100644 --- a/clang/include/clang/AST/PropertiesBase.td +++ b/clang/include/clang/AST/PropertiesBase.td @@ -117,6 +117,8 @@ def ExtParameterInfo : PropertyType<"FunctionProtoType::ExtParameterInfo">; def FixedPointSemantics : PropertyType<"llvm::FixedPointSemantics"> { let PassByReference = 1; } +def FunctionEffect : PropertyType<"FunctionEffect">; +def EffectConditionExpr : PropertyType<"EffectConditionExpr">; def Identifier : RefPropertyType<"IdentifierInfo"> { let ConstWhenWriting = 1; } def LValuePathEntry : PropertyType<"APValue::LValuePathEntry">; def LValuePathSerializationHelper : diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 61246479188e9..62836ec5c6312 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -118,6 +118,7 @@ class EnumDecl; class Expr; class ExtQualsTypeCommonBase; class FunctionDecl; +class FunctionEffectSet; class IdentifierInfo; class NamedDecl; class ObjCInterfaceDecl; @@ -131,6 +132,7 @@ class TemplateArgument; class TemplateArgumentListInfo; class TemplateArgumentLoc; class TemplateTypeParmDecl; +template <typename> class TreeTransform; class TypedefNameDecl; class UnresolvedUsingTypenameDecl; class UsingShadowDecl; @@ -4524,8 +4526,13 @@ class FunctionType : public Type { LLVM_PREFERRED_TYPE(bool) unsigned HasArmTypeAttributes : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned EffectsHaveConditions : 1; + unsigned NumFunctionEffects : 4; + FunctionTypeExtraBitfields() - : NumExceptionType(0), HasArmTypeAttributes(false) {} + : NumExceptionType(0), HasArmTypeAttributes(false), + EffectsHaveConditions(false), NumFunctionEffects(0) {} }; /// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number @@ -4658,6 +4665,296 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode { } }; +// ------------------------------------------------------------------------------ + +/// Represents an abstract function effect, using just an enumeration describing +/// its kind. +class FunctionEffect { +public: + /// Identifies the particular effect. + enum class Kind : uint8_t { + None = 0, + NonBlocking = 1, + NonAllocating = 2, + Blocking = 3, + Allocating = 4 + }; + + /// Flags describing some behaviors of the effect. + using Flags = unsigned; + enum FlagBit : Flags { + // Can verification inspect callees' implementations? (e.g. nonblocking: + // yes, tcb+types: no). This also implies the need for 2nd-pass + // verification. + FE_InferrableOnCallees = 0x1, + + // Language constructs which effects can diagnose as disallowed. + FE_ExcludeThrow = 0x2, + FE_ExcludeCatch = 0x4, + FE_ExcludeObjCMessageSend = 0x8, + FE_ExcludeStaticLocalVars = 0x10, + FE_ExcludeThreadLocalVars = 0x20 + }; + +private: + LLVM_PREFERRED_TYPE(Kind) + unsigned FKind : 3; + + // Expansion: for hypothetical TCB+types, there could be one Kind for TCB, + // then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would + // be considered for uniqueness. + +public: + FunctionEffect() : FKind(unsigned(Kind::None)) {} + + explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {} + + /// The kind of the effect. + Kind kind() const { return Kind(FKind); } + + /// Return the opposite kind, for effects which have opposites. + Kind oppositeKind() const; + + /// For serialization. + uint32_t toOpaqueInt32() const { return FKind; } + static FunctionEffect fromOpaqueInt32(uint32_t Value) { + return FunctionEffect(Kind(Value)); + } + + /// Flags describing some behaviors of the effect. + Flags flags() const { + switch (kind()) { + case Kind::NonBlocking: + return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch | + FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars | + FE_ExcludeThreadLocalVars; + case Kind::NonAllocating: + // Same as NonBlocking, except without FE_ExcludeStaticLocalVars. + return FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch | + FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars; + case Kind::Blocking: + case Kind::Allocating: + return 0; + case Kind::None: + break; + } + llvm_unreachable("unknown effect kind"); + } + + /// The description printed in diagnostics, e.g. 'nonblocking'. + StringRef name() const; + + /// Return true if the effect is allowed to be inferred on the callee, + /// which is either a FunctionDecl or BlockDecl. + /// Example: This allows nonblocking(false) to prevent inference for the + /// function. + bool canInferOnFunction(const Decl &Callee) const; + + // Return false for success. When true is returned for a direct call, then the + // FE_InferrableOnCallees flag may trigger inference rather than an immediate + // diagnostic. Caller should be assumed to have the effect (it may not have it + // explicitly when inferring). + bool shouldDiagnoseFunctionCall(bool Direct, + ArrayRef<FunctionEffect> CalleeFX) const; + + friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) { + return LHS.FKind == RHS.FKind; + } + friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) { + return !(LHS == RHS); + } + friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) { + return LHS.FKind < RHS.FKind; + } +}; + +/// Wrap a function effect's condition expression in another struct so +/// that FunctionProtoType's TrailingObjects can treat it separately. +class EffectConditionExpr { + Expr *Cond = nullptr; // if null, unconditional. + +public: + EffectConditionExpr() = default; + EffectConditionExpr(Expr *E) : Cond(E) {} + + Expr *getCondition() const { return Cond; } + + bool operator==(const EffectConditionExpr &RHS) const { + return Cond == RHS.Cond; + } +}; + +/// A FunctionEffect plus a potential boolean expression determining whether +/// the effect is declared (e.g. nonblocking(expr)). Generally the condition +/// expression when present, is dependent. +struct FunctionEffectWithCondition { + FunctionEffect Effect; + EffectConditionExpr Cond; + + FunctionEffectWithCondition() = default; + FunctionEffectWithCondition(const FunctionEffect &E, + const EffectConditionExpr &C) + : Effect(E), Cond(C) {} + + /// Return a textual description of the effect, and its condition, if any. + std::string description() const; +}; + +/// Support iteration in parallel through a pair of FunctionEffect and +/// EffectConditionExpr containers. +template <typename Container> class FunctionEffectIterator { + friend Container; + + const Container *Outer = nullptr; + size_t Idx = 0; + +public: + FunctionEffectIterator(); + FunctionEffectIterator(const Container &O, size_t I) : Outer(&O), Idx(I) {} + bool operator==(const FunctionEffectIterator &Other) const { + return Idx == Other.Idx; + } + bool operator!=(const FunctionEffectIterator &Other) const { + return Idx != Other.Idx; + } + + FunctionEffectIterator operator++() { + ++Idx; + return *this; + } + + FunctionEffectWithCondition operator*() const { + assert(Outer != nullptr && "invalid FunctionEffectIterator"); + bool HasConds = !Outer->Conditions.empty(); + return FunctionEffectWithCondition{Outer->Effects[Idx], + HasConds ? Outer->Conditions[Idx] + : EffectConditionExpr()}; + } +}; + +/// An immutable set of FunctionEffects and possibly conditions attached to +/// them. The effects and conditions reside in memory not managed by this object +/// (typically, trailing objects in FunctionProtoType, or borrowed references +/// from a FunctionEffectSet). +/// +/// Invariants: +/// - there is never more than one instance of any given effect. +/// - the array of conditions is either empty or has the same size as the +/// array of effects. +/// - some conditions may be null expressions; each condition pertains to +/// the effect at the same array index. +/// +/// Also, if there are any conditions, at least one of those expressions will be +/// dependent, but this is only asserted in the constructor of +/// FunctionProtoType. +/// +/// See also FunctionEffectSet, in Sema, which provides a mutable set. +class FunctionEffectsRef { + // Restrict classes which can call the private constructor -- these friends + // all maintain the required invariants. FunctionEffectSet is generally the + // only way in which the arrays are created; FunctionProtoType will not + // reorder them. + friend FunctionProtoType; + friend FunctionEffectSet; + + ArrayRef<FunctionEffect> Effects; + ArrayRef<EffectConditionExpr> Conditions; + + // The arrays are expected to have been sorted by the caller, with the + // effects in order. The conditions array must be empty or the same size + // as the effects array, since the conditions are associated with the effects + // at the same array indices. + FunctionEffectsRef(ArrayRef<FunctionEffect> FX, + ArrayRef<EffectConditionExpr> Conds) + : Effects(FX), Conditions(Conds) {} + +public: + /// Extract the effects from a Type if it is a function, block, or member + /// function pointer, or a reference or pointer to one. + static FunctionEffectsRef get(QualType QT); + + /// Asserts invariants. + static FunctionEffectsRef create(ArrayRef<FunctionEffect> FX, + ArrayRef<EffectConditionExpr> Conds); + + FunctionEffectsRef() = default; + + bool empty() const { return Effects.empty(); } + size_t size() const { return Effects.size(); } + + ArrayRef<FunctionEffect> effects() const { return Effects; } + ArrayRef<EffectConditionExpr> conditions() const { return Conditions; } + + using iterator = FunctionEffectIterator<FunctionEffectsRef>; + friend iterator; + iterator begin() const { return iterator(*this, 0); } + iterator end() const { return iterator(*this, size()); } + + friend bool operator==(const FunctionEffectsRef &LHS, + const FunctionEffectsRef &RHS) { + return LHS.Effects == RHS.Effects && LHS.Conditions == RHS.Conditions; + } + friend bool operator!=(const FunctionEffectsRef &LHS, + const FunctionEffectsRef &RHS) { + return !(LHS == RHS); + } + + void Profile(llvm::FoldingSetNodeID &ID) const; + void dump(llvm::raw_ostream &OS) const; +}; + +/// A mutable set of FunctionEffects and possibly conditions attached to them. +/// Used to compare and merge effects on declarations. +/// +/// Has the same invariants as FunctionEffectsRef. +class FunctionEffectSet { + SmallVector<FunctionEffect> Effects; + SmallVector<EffectConditionExpr> Conditions; + +public: + FunctionEffectSet() = default; + + explicit FunctionEffectSet(const FunctionEffectsRef &FX) + : Effects(FX.effects()), Conditions(FX.conditions()) {} + + bool empty() const { return Effects.empty(); } + size_t size() const { return Effects.size(); } + + using iterator = FunctionEffectIterator<FunctionEffectSet>; + friend iterator; + iterator begin() const { return iterator(*this, 0); } + iterator end() const { return iterator(*this, size()); } + + operator FunctionEffectsRef() const { return {Effects, Conditions}; } + + void dump(llvm::raw_ostream &OS) const; + + // Mutators + + // On insertion, a conflict occurs when attempting to insert an + // effect which is opposite an effect already in the set, or attempting + // to insert an effect which is already in the set but with a condition + // which is not identical. + struct Conflict { + FunctionEffectWithCondition Kept; + FunctionEffectWithCondition Rejected; + }; + using Conflicts = SmallVector<Conflict>; + + // Returns true for success (obviating a check of Errs.empty()). + bool insert(const FunctionEffectWithCondition &NewEC, Conflicts &Errs); + + // Returns true for success (obviating a check of Errs.empty()). + bool insert(const FunctionEffectsRef &Set, Conflicts &Errs); + + // Set operations + + static FunctionEffectSet getUnion(FunctionEffectsRef LHS, + FunctionEffectsRef RHS, Conflicts &Errs); + static FunctionEffectSet getIntersection(FunctionEffectsRef LHS, + FunctionEffectsRef RHS); +}; + /// Represents a prototype with parameter type info, e.g. /// 'int foo(int)' or 'int foo(void)'. 'void' is represented as having no /// parameters, not as having a single void parameter. Such a type can have @@ -4672,7 +4969,8 @@ class FunctionProtoType final FunctionProtoType, QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields, FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType, - Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers> { + Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, + FunctionEffect, EffectConditionExpr, Qualifiers> { friend class ASTContext; // ASTContext creates these. friend TrailingObjects; @@ -4703,9 +5001,15 @@ class FunctionProtoType final // an ExtParameterInfo for each of the parameters. Present if and // only if hasExtParameterInfos() is true. // + // * Optionally, an array of getNumFunctionEffects() FunctionEffect. + // Present only when getNumFunctionEffects() > 0 + // + // * Optionally, an array of getNumFunctionEffects() EffectConditionExpr. + // Present only when getNumFunctionEffectConditions() > 0. + // // * Optionally a Qualifiers object to represent extra qualifiers that can't - // be represented by FunctionTypeBitfields.FastTypeQuals. Present if and only - // if hasExtQualifiers() is true. + // be represented by FunctionTypeBitfields.FastTypeQuals. Present if and + // only if hasExtQualifiers() is true. // // The optional FunctionTypeExtraBitfields has to be before the data // related to the exception specification since it contains the number @@ -4761,6 +5065,7 @@ class FunctionProtoType final ExceptionSpecInfo ExceptionSpec; const ExtParameterInfo *ExtParameterInfos = nullptr; SourceLocation EllipsisLoc; + FunctionEffectsRef FunctionEffects; ExtProtoInfo() : Variadic(false), HasTrailingReturn(false), @@ -4778,7 +5083,8 @@ class FunctionProtoType final bool requiresFunctionProtoTypeExtraBitfields() const { return ExceptionSpec.Type == EST_Dynamic || - requiresFunctionProtoTypeArmAttributes(); + requiresFunctionProtoTypeArmAttributes() || + !FunctionEffects.empty(); } bool requiresFunctionProtoTypeArmAttributes() const { @@ -4826,6 +5132,14 @@ class FunctionProtoType final return hasExtParameterInfos() ? getNumParams() : 0; } + unsigned numTrailingObjects(OverloadToken<FunctionEffect>) const { + return getNumFunctionEffects(); + } + + unsigned numTrailingObjects(OverloadToken<EffectConditionExpr>) const { + return getNumFunctionEffectConditions(); + } + /// Determine whether there are any argument types that /// contain an unexpanded parameter pack. static bool containsAnyUnexpandedParameterPack(const QualType *ArgArray, @@ -4927,6 +5241,7 @@ class FunctionProtoType final EPI.RefQualifier = getRefQualifier(); EPI.ExtParameterInfos = getExtParameterInfosOrNull(); EPI.AArch64SMEAttributes = getAArch64SMEAttributes(); + EPI.FunctionEffects = getFunctionEffects(); return EPI; } @@ -5138,6 +5453,62 @@ class FunctionProtoType final return false; } + unsigned getNumFunctionEffects() const { + return hasExtraBitfields() + ? getTrailingObjects<FunctionTypeExtraBitfields>() + ->NumFunctionEffects + : 0; + } + + // For serialization. + ArrayRef<FunctionEffect> getFunctionEffectsWithoutConditions() const { + if (hasExtraBitfields()) { + const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>(); + if (Bitfields->NumFunctionEffects > 0) + return {getTrailingObjects<FunctionEffect>(), + Bitfields->NumFunctionEffects}; + } + return {}; + } + + unsigned getNumFunctionEffectConditions() const { + if (hasExtraBitfields()) { + const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>(); + if (Bitfields->EffectsHaveConditions) + return Bitfields->NumFunctionEffects; + } + return 0; + } + + // For serialization. + ArrayRef<EffectConditionExpr> getFunctionEffectConditions() const { + if (hasExtraBitfields()) { + const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>(); + if (Bitfields->EffectsHaveConditions) + return {getTrailingObjects<EffectConditionExpr>(), + Bitfields->NumFunctionEffects}; + } + return {}; + } + + // Combines effects with their conditions. + FunctionEffectsRef getFunctionEffects() const { + if (hasExtraBitfields()) { + const auto *Bitfields = getTrailingObjects<FunctionTypeExtraBitfields>(); + if (Bitfields->NumFunctionEffects > 0) { + const size_t NumConds = Bitfields->EffectsHaveConditions + ? Bitfields->NumFunctionEffects + : 0; + return FunctionEffectsRef( + {getTrailingObjects<FunctionEffect>(), + Bitfields->NumFunctionEffects}, + {NumConds ? getTrailingObjects<EffectConditionExpr>() : nullptr, + NumConds}); + } + } + return {}; + } + bool isSugared() const { return false; } QualType desugar() const { return QualType(this, 0); } diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td index aba14b222a03a..7d4353c2773a3 100644 --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -352,6 +352,12 @@ let Class = FunctionProtoType in { def : Property<"AArch64SMEAttributes", UInt32> { let Read = [{ node->getAArch64SMEAttributes() }]; } + def : Property<"functionEffects", Array<FunctionEffect>> { + let Read = [{ node->getFunctionEffectsWithoutConditions() }]; + } + def : Property<"functionEffectConds", Array<EffectConditionExpr>> { + let Read = [{ node->getFunctionEffectConditions() }]; + } def : Creator<[{ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, @@ -368,6 +374,7 @@ let Class = FunctionProtoType in { epi.ExtParameterInfos = extParameterInfo.empty() ? nullptr : extParameterInfo.data(); epi.AArch64SMEAttributes = AArch64SMEAttributes; + epi.FunctionEffects = FunctionEffectsRef::create(functionEffects, functionEffectConds); return ctx.getFunctionType(returnType, parameters, epi); }]>; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index c8e2015c8e66a..0c469e389eff0 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1461,6 +1461,28 @@ def CXX11NoReturn : InheritableAttr { let Documentation = [CXX11NoReturnDocs]; } +def NonBlocking : TypeAttr { + let Spellings = [Clang<"nonblocking">]; + let Args = [ExprArgument<"Cond", /*optional*/1>]; + let Documentation = [NonBlockingDocs]; +} + +def NonAllocating : TypeAttr { + let Spellings = [Clang<"nonallocating">]; + let Args = [ExprArgument<"Cond", /*optional*/1>]; + let Documentation = [NonAllocatingDocs]; +} + +def Blocking : TypeAttr { + let Spellings = [Clang<"blocking">]; + let Documentation = [BlockingDocs]; +} + +def Allocating : TypeAttr { + let Spellings = [Clang<"allocating">]; + let Documentation = [AllocatingDocs]; +} + // Similar to CUDA, OpenCL attributes do not receive a [[]] spelling because // the specification does not expose them with one currently. def OpenCLKernel : InheritableAttr { diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 9a523c99902d8..8d8f058281684 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -8107,3 +8107,72 @@ Attribute used by `clspv`_ (OpenCL-C to Vulkan SPIR-V compiler) to identify func .. _`libclc`: https://libclc.llvm.org }]; } + +def DocCatNonBlockingNonAllocating : DocumentationCategory<"Performance Constraint Attributes"> { + let Content = [{ +The ``nonblocking``, ``blocking``, ``nonallocating`` and ``allocating`` attributes can be attached +to function types, including blocks, C++ lambdas, and member functions. The attributes declare +constraints about a function's behavior pertaining to blocking and heap memory allocation. + +There are several rules for function types with these attributes, enforced with +compiler warnings: + +- When assigning or otherwise converting to a function pointer of ``nonblocking`` or + ``nonallocating`` type, the source must also be a function or function pointer of + that type, unless it is a null pointer, i.e. the attributes should not be "spoofed". Conversions + that remove the attributes are transparent and valid. + +- An override of a ``nonblocking`` or ``nonallocating`` virtual method must also be declared + with that same attribute (or a stronger one.) An overriding method may add an attribute. + +- A redeclaration of a ``nonblocking`` or ``nonallocating`` function must also be declared with + the same attribute (or a stronger one). A redeclaration may add an attribute. + +The warnings are controlled by ``-Wfunction-effects``, which is enabled by default. + +In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating`` +functions to other functions which lack the appropriate attribute. + }]; +} + +def NonBlockingDocs : Documentation { + let Category = DocCatNonBlockingNonAllocating; + let Heading = "nonblocking"; + let Content = [{ +Declares that a function or function type either does or does not block in any way, according +to the optional, compile-time constant boolean argument, which defaults to true. When the argument +is false, the attribute is equivalent to ``blocking``. + +For the purposes of diagnostics, ``nonblocking`` is considered to include the +``nonallocating`` guarantee and is therefore a "stronger" constraint or attribute. + }]; +} + +def NonAllocatingDocs : Documentation { + let Category = DocCatNonBlockingNonAllocating; + let Heading = "nonallocating"; + let Content = [{ +Declares that a function or function type either does or does not allocate heap memory, according +to the optional, compile-time constant boolean argument, which defaults to true. When the argument +is false, the attribute is equivalent to ``allocating``. + }]; +} + +def BlockingDocs : Documentation { + let Category = DocCatNonBlockingNonAllocating; + let Heading = "blocking"; + let Content = [{ +Declares that a function potentially blocks, and prevents any potential inference of ``nonblocking`` +by the compiler. + }]; +} + +def AllocatingDocs : Documentation { + let Category = DocCatNonBlockingNonAllocating; + let Heading = "allocating"; + let Content = [{ +Declares that a function potentially allocates heap memory, and prevents any potential inference +of ``nonallocating`` by the compiler. + }]; +} + diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 9b37d4bd3205b..1c4f305fb5d00 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1527,6 +1527,10 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">; def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">; def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>; +// Warnings and notes related to the function effects system underlying +// the nonblocking and nonallocating attributes. +def FunctionEffects : DiagGroup<"function-effects">; + // Warnings and notes InstallAPI verification. def InstallAPIViolation : DiagGroup<"installapi-violation">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 25a87078a5709..f323d1c6eaf1b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10881,6 +10881,23 @@ def warn_imp_cast_drops_unaligned : Warning< "implicit cast from type %0 to type %1 drops __unaligned qualifier">, InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>; +// Function effects +// spoofing nonblocking/nonallocating +def warn_invalid_add_func_effects : Warning< + "attribute '%0' should not be added via type conversion">, + InGroup<FunctionEffects>; +def warn_mismatched_func_effect_override : Warning< + "attribute '%0' on overriding function does not match base declaration">, + InGroup<FunctionEffects>; +def warn_mismatched_func_effect_redeclaration : Warning< + "attribute '%0' on function does not match previous declaration">, + InGroup<FunctionEffects>; +def warn_conflicting_func_effects : Warning< + "effects conflict when merging declarations; kept '%0', discarded '%1'">, + InGroup<FunctionEffects>; +def err_func_with_effects_no_prototype : Error< + "'%0' function must have a prototype">; + } // end of sema category let CategoryName = "API Notes Issue" in { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index e43e5f465361d..2e7af0f691cbb 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -473,6 +473,63 @@ enum class TagUseKind { Friend // Friend declaration: 'friend struct foo;' }; +/// Used with attributes/effects with a boolean condition, e.g. `nonblocking`. +enum class FunctionEffectMode : uint8_t { + None, // effect is not present. + False, // effect(false). + True, // effect(true). + Dependent // effect(expr) where expr is dependent. +}; + +struct FunctionEffectDiff { + enum class Kind { Added, Removed, ConditionMismatch }; + + FunctionEffect::Kind EffectKind; + Kind DiffKind; + FunctionEffectWithCondition Old; // invalid when Added. + FunctionEffectWithCondition New; // invalid when Removed. + + StringRef effectName() const { + if (Old.Effect.kind() != FunctionEffect::Kind::None) + return Old.Effect.name(); + return New.Effect.name(); + } + + /// Describes the result of effects diff ering between a base class's virtual + /// method and an overriding method in a subclass. + enum class OverrideResult { + NoAction, + Warn, + Merge // Merge missing effect from base to derived. + }; + + /// Return true if adding or removing the effect as part of a type conversion + /// should generate a diagnostic. + bool shouldDiagnoseConversion(QualType SrcType, + const FunctionEffectsRef &SrcFX, + QualType DstType, + const FunctionEffectsRef &DstFX) const; + + /// Return true if adding or removing the effect in a redeclaration should + /// generate a diagnostic. + bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction, + const FunctionEffectsRef &OldFX, + const FunctionDecl &NewFunction, + const FunctionEffectsRef &NewFX) const; + + /// Return true if adding or removing the effect in a C++ virtual method + /// override should generate a diagnostic. + OverrideResult shouldDiagnoseMethodOverride( + const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, + const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const; +}; + +struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> { + /// Caller should short-circuit by checking for equality first. + FunctionEffectDifferences(const FunctionEffectsRef &Old, + const FunctionEffectsRef &New); +}; + /// Sema - This implements semantic analysis and AST building for C. /// \nosubgrouping class Sema final : public SemaBase { @@ -783,6 +840,28 @@ class Sema final : public SemaBase { /// Warn when implicitly casting 0 to nullptr. void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E); + // ----- function effects --- + + /// Warn when implicitly changing function effects. + void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, + SourceLocation Loc); + + /// Warn and return true if adding an effect to a set would create a conflict. + bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX, + const FunctionEffectWithCondition &EC, + SourceLocation NewAttrLoc); + + void + diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs, + SourceLocation NewLoc, + SourceLocation OldLoc); + + /// Try to parse the conditional expression attached to an effect attribute + /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty + /// optional on error. + std::optional<FunctionEffectMode> + ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName); + bool makeUnavailableInSystemHeader(SourceLocation loc, UnavailableAttr::ImplicitReason reason); @@ -4612,7 +4691,7 @@ class Sema final : public SemaBase { std::string getAmbiguousPathsDisplayString(CXXBasePaths &Paths); - bool CheckOverridingFunctionAttributes(const CXXMethodDecl *New, + bool CheckOverridingFunctionAttributes(CXXMethodDecl *New, const CXXMethodDecl *Old); /// CheckOverridingFunctionReturnType - Checks whether the return types are diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index be24c161d6714..1b5d16bd176f3 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -4601,11 +4601,13 @@ QualType ASTContext::getFunctionTypeInternal( size_t Size = FunctionProtoType::totalSizeToAlloc< QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields, FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType, - Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers>( + Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, + FunctionEffect, EffectConditionExpr, Qualifiers>( NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(), EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType, ESH.NumExprPtr, ESH.NumFunctionDeclPtr, - EPI.ExtParameterInfos ? NumArgs : 0, + EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects.size(), + EPI.FunctionEffects.conditions().size(), EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0); auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType)); @@ -10550,6 +10552,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs, FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn); + std::optional<FunctionEffectSet> MergedFX; + if (lproto && rproto) { // two C99 style function prototypes assert((AllowCXX || (!lproto->hasExceptionSpec() && !rproto->hasExceptionSpec())) && @@ -10565,6 +10569,25 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs, if (lproto->getMethodQuals() != rproto->getMethodQuals()) return {}; + // Function effects are handled similarly to noreturn, see above. + FunctionEffectsRef LHSFX = lproto->getFunctionEffects(); + FunctionEffectsRef RHSFX = rproto->getFunctionEffects(); + if (LHSFX != RHSFX) { + if (IsConditionalOperator) + MergedFX = FunctionEffectSet::getIntersection(LHSFX, RHSFX); + else { + FunctionEffectSet::Conflicts Errs; + MergedFX = FunctionEffectSet::getUnion(LHSFX, RHSFX, Errs); + // Here we're discarding a possible error due to conflicts in the effect + // sets. But we're not in a context where we can report it. The + // operation does however guarantee maintenance of invariants. + } + if (*MergedFX != LHSFX) + allLTypes = false; + if (*MergedFX != RHSFX) + allRTypes = false; + } + SmallVector<FunctionProtoType::ExtParameterInfo, 4> newParamInfos; bool canUseLeft, canUseRight; if (!mergeExtParameterInfo(lproto, rproto, canUseLeft, canUseRight, @@ -10608,6 +10631,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs, EPI.ExtInfo = einfo; EPI.ExtParameterInfos = newParamInfos.empty() ? nullptr : newParamInfos.data(); + if (MergedFX) + EPI.FunctionEffects = *MergedFX; return getFunctionType(retType, types, EPI); } @@ -10645,6 +10670,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs, FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo(); EPI.ExtInfo = einfo; + if (MergedFX) + EPI.FunctionEffects = *MergedFX; return getFunctionType(retType, proto->getParamTypes(), EPI); } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 656b733a13b0e..d8b885870de3a 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -3711,6 +3711,34 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params, auto &EllipsisLoc = *getTrailingObjects<SourceLocation>(); EllipsisLoc = epi.EllipsisLoc; } + + if (!epi.FunctionEffects.empty()) { + auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>(); + size_t EffectsCount = epi.FunctionEffects.size(); + ExtraBits.NumFunctionEffects = EffectsCount; + assert(ExtraBits.NumFunctionEffects == EffectsCount && + "effect bitfield overflow"); + + ArrayRef<FunctionEffect> SrcFX = epi.FunctionEffects.effects(); + auto *DestFX = getTrailingObjects<FunctionEffect>(); + std::uninitialized_copy(SrcFX.begin(), SrcFX.end(), DestFX); + + ArrayRef<EffectConditionExpr> SrcConds = epi.FunctionEffects.conditions(); + if (!SrcConds.empty()) { + ExtraBits.EffectsHaveConditions = true; + auto *DestConds = getTrailingObjects<EffectConditionExpr>(); + std::uninitialized_copy(SrcConds.begin(), SrcConds.end(), DestConds); + assert(std::any_of(SrcConds.begin(), SrcConds.end(), + [](const EffectConditionExpr &EC) { + if (const Expr *E = EC.getCondition()) + return E->isTypeDependent() || + E->isValueDependent(); + return false; + }) && + "expected a dependent expression among the conditions"); + addDependence(TypeDependence::DependentInstantiation); + } + } } bool FunctionProtoType::hasDependentExceptionSpec() const { @@ -3794,6 +3822,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result, // Finally we have a trailing return type flag (bool) // combined with AArch64 SME Attributes, to save space: // int + // combined with any FunctionEffects // // There is no ambiguity between the consumed arguments and an empty EH // spec because of the leading 'bool' which unambiguously indicates @@ -3829,6 +3858,8 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result, epi.ExtInfo.Profile(ID); ID.AddInteger((epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn); + + epi.FunctionEffects.Profile(ID); } void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, @@ -5093,3 +5124,257 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) { Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(), getTypeConstraintConcept(), getTypeConstraintArguments()); } + +FunctionEffect::Kind FunctionEffect::oppositeKind() const { + switch (kind()) { + case Kind::NonBlocking: + return Kind::Blocking; + case Kind::Blocking: + return Kind::NonBlocking; + case Kind::NonAllocating: + return Kind::Allocating; + case Kind::Allocating: + return Kind::NonAllocating; + case Kind::None: + return Kind::None; + } + llvm_unreachable("unknown effect kind"); +} + +StringRef FunctionEffect::name() const { + switch (kind()) { + case Kind::NonBlocking: + return "nonblocking"; + case Kind::NonAllocating: + return "nonallocating"; + case Kind::Blocking: + return "blocking"; + case Kind::Allocating: + return "allocating"; + case Kind::None: + return "(none)"; + } + llvm_unreachable("unknown effect kind"); +} + +bool FunctionEffect::canInferOnFunction(const Decl &Callee) const { + switch (kind()) { + case Kind::NonAllocating: + case Kind::NonBlocking: { + FunctionEffectsRef CalleeFX; + if (auto *FD = Callee.getAsFunction()) + CalleeFX = FD->getFunctionEffects(); + else if (auto *BD = dyn_cast<BlockDecl>(&Callee)) + CalleeFX = BD->getFunctionEffects(); + else + return false; + for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) { + // nonblocking/nonallocating cannot call allocating. + if (CalleeEC.Effect.kind() == Kind::Allocating) + return false; + // nonblocking cannot call blocking. + if (kind() == Kind::NonBlocking && + CalleeEC.Effect.kind() == Kind::Blocking) + return false; + } + return true; + } + + case Kind::Allocating: + case Kind::Blocking: + return false; + + case Kind::None: + assert(0 && "canInferOnFunction with None"); + break; + } + llvm_unreachable("unknown effect kind"); +} + +bool FunctionEffect::shouldDiagnoseFunctionCall( + bool Direct, ArrayRef<FunctionEffect> CalleeFX) const { + switch (kind()) { + case Kind::NonAllocating: + case Kind::NonBlocking: { + const Kind CallerKind = kind(); + for (const auto &Effect : CalleeFX) { + const Kind EK = Effect.kind(); + // Does callee have same or stronger constraint? + if (EK == CallerKind || + (CallerKind == Kind::NonAllocating && EK == Kind::NonBlocking)) { + return false; // no diagnostic + } + } + return true; // warning + } + case Kind::Allocating: + case Kind::Blocking: + return false; + case Kind::None: + assert(0 && "shouldDiagnoseFunctionCall with None"); + break; + } + llvm_unreachable("unknown effect kind"); +} + +// ===== + +void FunctionEffectsRef::Profile(llvm::FoldingSetNodeID &ID) const { + bool HasConds = !Conditions.empty(); + + ID.AddInteger(size() | (HasConds << 31u)); + for (unsigned Idx = 0, Count = Effects.size(); Idx != Count; ++Idx) { + ID.AddInteger(Effects[Idx].toOpaqueInt32()); + if (HasConds) + ID.AddPointer(Conditions[Idx].getCondition()); + } +} + +bool FunctionEffectSet::insert(const FunctionEffectWithCondition &NewEC, + Conflicts &Errs) { + FunctionEffect::Kind NewOppositeKind = NewEC.Effect.oppositeKind(); + Expr *NewCondition = NewEC.Cond.getCondition(); + + // The index at which insertion will take place; default is at end + // but we might find an earlier insertion point. + unsigned InsertIdx = Effects.size(); + unsigned Idx = 0; + for (const FunctionEffectWithCondition &EC : *this) { + // Note about effects with conditions: They are considered distinct from + // those without conditions; they are potentially unique, redundant, or + // in conflict, but we can't tell which until the condition is evaluated. + if (EC.Cond.getCondition() == nullptr && NewCondition == nullptr) { + if (EC.Effect.kind() == NewEC.Effect.kind()) { + // There is no condition, and the effect kind is already present, + // so just fail to insert the new one (creating a duplicate), + // and return success. + return true; + } + + if (EC.Effect.kind() == NewOppositeKind) { + Errs.push_back({EC, NewEC}); + return false; + } + } + + if (NewEC.Effect.kind() < EC.Effect.kind() && InsertIdx > Idx) + InsertIdx = Idx; + + ++Idx; + } + + if (NewCondition || !Conditions.empty()) { + if (Conditions.empty() && !Effects.empty()) + Conditions.resize(Effects.size()); + Conditions.insert(Conditions.begin() + InsertIdx, + NewEC.Cond.getCondition()); + } + Effects.insert(Effects.begin() + InsertIdx, NewEC.Effect); + return true; +} + +bool FunctionEffectSet::insert(const FunctionEffectsRef &Set, Conflicts &Errs) { + for (const auto &Item : Set) + insert(Item, Errs); + return Errs.empty(); +} + +FunctionEffectSet FunctionEffectSet::getIntersection(FunctionEffectsRef LHS, + FunctionEffectsRef RHS) { + FunctionEffectSet Result; + FunctionEffectSet::Conflicts Errs; + + // We could use std::set_intersection but that would require expanding the + // container interface to include push_back, making it available to clients + // who might fail to maintain invariants. + auto IterA = LHS.begin(), EndA = LHS.end(); + auto IterB = RHS.begin(), EndB = RHS.end(); + + auto FEWCLess = [](const FunctionEffectWithCondition &LHS, + const FunctionEffectWithCondition &RHS) { + return std::tuple(LHS.Effect, uintptr_t(LHS.Cond.getCondition())) < + std::tuple(RHS.Effect, uintptr_t(RHS.Cond.getCondition())); + }; + + while (IterA != EndA && IterB != EndB) { + FunctionEffectWithCondition A = *IterA; + FunctionEffectWithCondition B = *IterB; + if (FEWCLess(A, B)) + ++IterA; + else if (FEWCLess(B, A)) + ++IterB; + else { + Result.insert(A, Errs); + ++IterA; + ++IterB; + } + } + + // Insertion shouldn't be able to fail; that would mean both input + // sets contained conflicts. + assert(Errs.empty() && "conflict shouldn't be possible in getIntersection"); + + return Result; +} + +FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS, + FunctionEffectsRef RHS, + Conflicts &Errs) { + // Optimize for either of the two sets being empty (very common). + if (LHS.empty()) + return FunctionEffectSet(RHS); + + FunctionEffectSet Combined(LHS); + Combined.insert(RHS, Errs); + return Combined; +} + +LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const { + OS << "Effects{"; + bool First = true; + for (const auto &CFE : *this) { + if (!First) + OS << ", "; + else + First = false; + OS << CFE.Effect.name(); + if (Expr *E = CFE.Cond.getCondition()) { + OS << '('; + E->dump(); + OS << ')'; + } + } + OS << "}"; +} + +LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const { + FunctionEffectsRef(*this).dump(OS); +} + +FunctionEffectsRef FunctionEffectsRef::get(QualType QT) { + while (true) { + QualType Pointee = QT->getPointeeType(); + if (Pointee.isNull()) + break; + QT = Pointee; + } + if (const auto *FPT = QT->getAs<FunctionProtoType>()) + return FPT->getFunctionEffects(); + return {}; +} + +FunctionEffectsRef +FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX, + ArrayRef<EffectConditionExpr> Conds) { + assert(std::is_sorted(FX.begin(), FX.end()) && "effects should be sorted"); + assert((Conds.empty() || Conds.size() == FX.size()) && + "effects size should match conditions size"); + return FunctionEffectsRef(FX, Conds); +} + +std::string FunctionEffectWithCondition::description() const { + std::string Result(Effect.name().str()); + if (Cond.getCondition() != nullptr) + Result += "(expr)"; + return Result; +} diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index 4add4d3af69a3..7c87fd587880e 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1016,6 +1016,17 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T, } T->printExceptionSpecification(OS, Policy); + const FunctionEffectsRef FX = T->getFunctionEffects(); + for (const auto &CFE : FX) { + OS << " __attribute__((" << CFE.Effect.name(); + if (const Expr *E = CFE.Cond.getCondition()) { + OS << '('; + E->printPretty(OS, nullptr, Policy); + OS << ')'; + } + OS << "))"; + } + if (T->hasTrailingReturn()) { OS << " -> "; print(T->getReturnType(), OS, StringRef()); @@ -1946,6 +1957,10 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::ArmOut: case attr::ArmInOut: case attr::ArmPreserves: + case attr::NonBlocking: + case attr::NonAllocating: + case attr::Blocking: + case attr::Allocating: llvm_unreachable("This attribute should have been handled already"); case attr::NSReturnsRetained: diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index 069978c1b4023..3f8f2f027172d 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -638,6 +638,19 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType, Diag(Loc, diag::warn_nullability_lost) << SrcType << DstType; } +// Generate diagnostics when adding or removing effects in a type conversion. +void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType, + SourceLocation Loc) { + const auto SrcFX = FunctionEffectsRef::get(SrcType); + const auto DstFX = FunctionEffectsRef::get(DstType); + if (SrcFX != DstFX) { + for (const auto &Diff : FunctionEffectDifferences(SrcFX, DstFX)) { + if (Diff.shouldDiagnoseConversion(SrcType, SrcFX, DstType, DstFX)) + Diag(Loc, diag::warn_invalid_add_func_effects) << Diff.effectName(); + } + } +} + void Sema::diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E) { // nullptr only exists from C++11 on, so don't warn on its absence earlier. if (!getLangOpts().CPlusPlus11) @@ -715,6 +728,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty, diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc()); diagnoseZeroToNullptrConversion(Kind, E); + if (!isCast(CCK) && Kind != CK_NullToPointer && + Kind != CK_NullToMemberPointer) + diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc()); QualType ExprTy = Context.getCanonicalType(E->getType()); QualType TypeTy = Context.getCanonicalType(Ty); @@ -2796,3 +2812,153 @@ bool Sema::isDeclaratorFunctionLike(Declarator &D) { }); return Result; } + +FunctionEffectDifferences::FunctionEffectDifferences( + const FunctionEffectsRef &Old, const FunctionEffectsRef &New) { + + FunctionEffectsRef::iterator POld = Old.begin(); + FunctionEffectsRef::iterator OldEnd = Old.end(); + FunctionEffectsRef::iterator PNew = New.begin(); + FunctionEffectsRef::iterator NewEnd = New.end(); + + while (true) { + int cmp = 0; + if (POld == OldEnd) { + if (PNew == NewEnd) + break; + cmp = 1; + } else if (PNew == NewEnd) + cmp = -1; + else { + FunctionEffectWithCondition Old = *POld; + FunctionEffectWithCondition New = *PNew; + if (Old.Effect.kind() < New.Effect.kind()) + cmp = -1; + else if (New.Effect.kind() < Old.Effect.kind()) + cmp = 1; + else { + cmp = 0; + if (Old.Cond.getCondition() != New.Cond.getCondition()) { + // FIXME: Cases where the expressions are equivalent but + // don't have the same identity. + push_back(FunctionEffectDiff{ + Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch, + Old, New}); + } + } + } + + if (cmp < 0) { + // removal + FunctionEffectWithCondition Old = *POld; + push_back(FunctionEffectDiff{ + Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}}); + ++POld; + } else if (cmp > 0) { + // addition + FunctionEffectWithCondition New = *PNew; + push_back(FunctionEffectDiff{ + New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New}); + ++PNew; + } else { + ++POld; + ++PNew; + } + } +} + +bool FunctionEffectDiff::shouldDiagnoseConversion( + QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType, + const FunctionEffectsRef &DstFX) const { + + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + // nonallocating can't be added (spoofed) during a conversion, unless we + // have nonblocking. + if (DiffKind == Kind::Added) { + for (const auto &CFE : SrcFX) { + if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking) + return false; + } + } + [[fallthrough]]; + case FunctionEffect::Kind::NonBlocking: + // nonblocking can't be added (spoofed) during a conversion. + switch (DiffKind) { + case Kind::Added: + return true; + case Kind::Removed: + return false; + case Kind::ConditionMismatch: + // FIXME: Condition mismatches are too coarse right now -- expressions + // which are equivalent but don't have the same identity are detected as + // mismatches. We're going to diagnose those anyhow until expression + // matching is better. + return true; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + case FunctionEffect::Kind::None: + break; + } + llvm_unreachable("unknown effect kind"); +} + +bool FunctionEffectDiff::shouldDiagnoseRedeclaration( + const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX, + const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const { + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + case FunctionEffect::Kind::NonBlocking: + // nonblocking/nonallocating can't be removed in a redeclaration. + switch (DiffKind) { + case Kind::Added: + return false; // No diagnostic. + case Kind::Removed: + return true; // Issue diagnostic. + case Kind::ConditionMismatch: + // All these forms of mismatches are diagnosed. + return true; + } + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return false; + case FunctionEffect::Kind::None: + break; + } + llvm_unreachable("unknown effect kind"); +} + +FunctionEffectDiff::OverrideResult +FunctionEffectDiff::shouldDiagnoseMethodOverride( + const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX, + const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const { + switch (EffectKind) { + case FunctionEffect::Kind::NonAllocating: + case FunctionEffect::Kind::NonBlocking: + switch (DiffKind) { + + // If added on an override, that's fine and not diagnosed. + case Kind::Added: + return OverrideResult::NoAction; + + // If missing from an override (removed), propagate from base to derived. + case Kind::Removed: + return OverrideResult::Merge; + + // If there's a mismatch involving the effect's polarity or condition, + // issue a warning. + case Kind::ConditionMismatch: + return OverrideResult::Warn; + } + + case FunctionEffect::Kind::Blocking: + case FunctionEffect::Kind::Allocating: + return OverrideResult::NoAction; + + case FunctionEffect::Kind::None: + break; + } + llvm_unreachable("unknown effect kind"); +} diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index efd546a6a3817..029ccf944c513 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3911,6 +3911,49 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S, return true; } + const auto OldFX = Old->getFunctionEffects(); + const auto NewFX = New->getFunctionEffects(); + QualType OldQTypeForComparison = OldQType; + if (OldFX != NewFX) { + const auto Diffs = FunctionEffectDifferences(OldFX, NewFX); + for (const auto &Diff : Diffs) { + if (Diff.shouldDiagnoseRedeclaration(*Old, OldFX, *New, NewFX)) { + Diag(New->getLocation(), + diag::warn_mismatched_func_effect_redeclaration) + << Diff.effectName(); + Diag(Old->getLocation(), diag::note_previous_declaration); + } + } + // Following a warning, we could skip merging effects from the previous + // declaration, but that would trigger an additional "conflicting types" + // error. + if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) { + FunctionEffectSet::Conflicts MergeErrs; + FunctionEffectSet MergedFX = + FunctionEffectSet::getUnion(OldFX, NewFX, MergeErrs); + if (!MergeErrs.empty()) + diagnoseFunctionEffectMergeConflicts(MergeErrs, New->getLocation(), + Old->getLocation()); + + FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo(); + EPI.FunctionEffects = FunctionEffectsRef(MergedFX); + QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(), + NewFPT->getParamTypes(), EPI); + + New->setType(ModQT); + NewQType = New->getType(); + + // Revise OldQTForComparison to include the merged effects, + // so as not to fail due to diff erences later. + if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) { + EPI = OldFPT->getExtProtoInfo(); + EPI.FunctionEffects = FunctionEffectsRef(MergedFX); + OldQTypeForComparison = Context.getFunctionType( + OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI); + } + } + } + if (getLangOpts().CPlusPlus) { OldQType = Context.getCanonicalType(Old->getType()); NewQType = Context.getCanonicalType(New->getType()); @@ -4075,9 +4118,8 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S, // We also want to respect all the extended bits except noreturn. // noreturn should now match unless the old type info didn't have it. - QualType OldQTypeForComparison = OldQType; if (!OldTypeInfo.getNoReturn() && NewTypeInfo.getNoReturn()) { - auto *OldType = OldQType->castAs<FunctionProtoType>(); + auto *OldType = OldQTypeForComparison->castAs<FunctionProtoType>(); const FunctionType *OldTypeForComparison = Context.adjustFunctionType(OldType, OldTypeInfo.withNoReturn(true)); OldQTypeForComparison = QualType(OldTypeForComparison, 0); @@ -20546,3 +20588,62 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) { return LangOpts.CUDA && !LangOpts.CUDAIsDevice && CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global; } + +// Report a failure to merge function effects between declarations due to a +// conflict. +void Sema::diagnoseFunctionEffectMergeConflicts( + const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc, + SourceLocation OldLoc) { + for (const FunctionEffectSet::Conflict &Conflict : Errs) { + Diag(NewLoc, diag::warn_conflicting_func_effects) + << Conflict.Kept.description() << Conflict.Rejected.description(); + Diag(OldLoc, diag::note_previous_declaration); + } +} + +// Warn and return true if adding an effect to a set would create a conflict. +bool Sema::diagnoseConflictingFunctionEffect( + const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC, + SourceLocation NewAttrLoc) { + // If the new effect has a condition, we can't detect conflicts until the + // condition is resolved. + if (NewEC.Cond.getCondition() != nullptr) + return false; + + // Diagnose the new attribute as incompatible with a previous one. + auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) { + Diag(NewAttrLoc, diag::err_attributes_are_not_compatible) + << ("'" + NewEC.description() + "'") + << ("'" + PrevEC.description() + "'") << false; + // We don't necessarily have the location of the previous attribute, + // so no note. + return true; + }; + + // Compare against previous attributes. + FunctionEffect::Kind NewKind = NewEC.Effect.kind(); + + for (const FunctionEffectWithCondition &PrevEC : FX) { + // Again, can't check yet when the effect is conditional. + if (PrevEC.Cond.getCondition() != nullptr) + continue; + + FunctionEffect::Kind PrevKind = PrevEC.Effect.kind(); + // Note that we allow PrevKind == NewKind; it's redundant and ignored. + + if (PrevEC.Effect.oppositeKind() == NewKind) + return Incompatible(PrevEC); + + // A new allocating is incompatible with a previous nonblocking. + if (PrevKind == FunctionEffect::Kind::NonBlocking && + NewKind == FunctionEffect::Kind::Allocating) + return Incompatible(PrevEC); + + // A new nonblocking is incompatible with a previous allocating. + if (PrevKind == FunctionEffect::Kind::Allocating && + NewKind == FunctionEffect::Kind::NonBlocking) + return Incompatible(PrevEC); + } + + return false; +} diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 234e89b91da07..9b220103247dd 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18291,7 +18291,7 @@ void Sema::SetFunctionBodyKind(Decl *D, SourceLocation Loc, FnBodyKind BodyKind, } } -bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New, +bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New, const CXXMethodDecl *Old) { const auto *NewFT = New->getType()->castAs<FunctionProtoType>(); const auto *OldFT = Old->getType()->castAs<FunctionProtoType>(); @@ -18327,6 +18327,41 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New, return true; } + // Virtual overrides: check for matching effects. + const auto OldFX = Old->getFunctionEffects(); + const auto NewFXOrig = New->getFunctionEffects(); + + if (OldFX != NewFXOrig) { + FunctionEffectSet NewFX(NewFXOrig); + const auto Diffs = FunctionEffectDifferences(OldFX, NewFX); + FunctionEffectSet::Conflicts Errs; + for (const auto &Diff : Diffs) { + switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) { + case FunctionEffectDiff::OverrideResult::NoAction: + break; + case FunctionEffectDiff::OverrideResult::Warn: + Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) + << Diff.effectName(); + Diag(Old->getLocation(), diag::note_overridden_virtual_function) + << Old->getReturnTypeSourceRange(); + break; + case FunctionEffectDiff::OverrideResult::Merge: { + NewFX.insert(Diff.Old, Errs); + const auto *NewFT = New->getType()->castAs<FunctionProtoType>(); + FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo(); + EPI.FunctionEffects = FunctionEffectsRef(NewFX); + QualType ModQT = Context.getFunctionType(NewFT->getReturnType(), + NewFT->getParamTypes(), EPI); + New->setType(ModQT); + break; + } + } + } + if (!Errs.empty()) + diagnoseFunctionEffectMergeConflicts(Errs, New->getLocation(), + Old->getLocation()); + } + CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv(); // If the calling conventions match, everything is fine diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index fb4ff72e42eb5..db77e5cfc1957 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1877,6 +1877,27 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType, FromFn = QT->getAs<FunctionType>(); Changed = true; } + + // For C, when called from checkPointerTypesForAssignment, + // we need to not alter FromFn, or else even an innocuous cast + // like dropping effects will fail. In C++ however we do want to + // alter FromFn (because of the way PerformImplicitConversion works). + if (getLangOpts().CPlusPlus) { + FromFPT = cast<FunctionProtoType>(FromFn); // in case FromFn changed above + + // Transparently add/drop effects; here we are concerned with + // language rules/canonicalization. Adding/dropping effects is a warning. + const auto FromFX = FromFPT->getFunctionEffects(); + const auto ToFX = ToFPT->getFunctionEffects(); + if (FromFX != ToFX) { + FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo(); + ExtInfo.FunctionEffects = ToFX; + QualType QT = Context.getFunctionType( + FromFPT->getReturnType(), FromFPT->getParamTypes(), ExtInfo); + FromFn = QT->getAs<FunctionType>(); + Changed = true; + } + } } if (!Changed) diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index 9bb12c6aa7b12..426cd0aa91c01 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -31,6 +31,7 @@ #include "clang/Sema/DeclSpec.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/ParsedAttr.h" #include "clang/Sema/ParsedTemplate.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaCUDA.h" @@ -149,6 +150,10 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr, #define FUNCTION_TYPE_ATTRS_CASELIST \ case ParsedAttr::AT_NSReturnsRetained: \ case ParsedAttr::AT_NoReturn: \ + case ParsedAttr::AT_NonBlocking: \ + case ParsedAttr::AT_NonAllocating: \ + case ParsedAttr::AT_Blocking: \ + case ParsedAttr::AT_Allocating: \ case ParsedAttr::AT_Regparm: \ case ParsedAttr::AT_CmseNSCall: \ case ParsedAttr::AT_ArmStreaming: \ @@ -7522,6 +7527,111 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) { llvm_unreachable("unexpected attribute kind!"); } +std::optional<FunctionEffectMode> +Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) { + if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) + return FunctionEffectMode::Dependent; + + std::optional<llvm::APSInt> ConditionValue = + CondExpr->getIntegerConstantExpr(Context); + if (!ConditionValue) { + // FIXME: err_attribute_argument_type doesn't quote the attribute + // name but needs to; users are inconsistent. + Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type) + << AttributeName << AANT_ArgumentIntegerConstant + << CondExpr->getSourceRange(); + return std::nullopt; + } + return !ConditionValue->isZero() ? FunctionEffectMode::True + : FunctionEffectMode::False; +} + +static bool +handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &TPState, + ParsedAttr &PAttr, QualType &QT, + FunctionTypeUnwrapper &Unwrapped) { + // Delay if this is not a function type. + if (!Unwrapped.isFunctionType()) + return false; + + Sema &S = TPState.getSema(); + + // Require FunctionProtoType. + auto *FPT = Unwrapped.get()->getAs<FunctionProtoType>(); + if (FPT == nullptr) { + S.Diag(PAttr.getLoc(), diag::err_func_with_effects_no_prototype) + << PAttr.getAttrName()->getName(); + return true; + } + + // Parse the new attribute. + // non/blocking or non/allocating? Or conditional (computed)? + bool IsNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking || + PAttr.getKind() == ParsedAttr::AT_Blocking; + + FunctionEffectMode NewMode = FunctionEffectMode::None; + Expr *CondExpr = nullptr; // only valid if dependent + + if (PAttr.getKind() == ParsedAttr::AT_NonBlocking || + PAttr.getKind() == ParsedAttr::AT_NonAllocating) { + if (!PAttr.checkAtMostNumArgs(S, 1)) { + PAttr.setInvalid(); + return true; + } + + // Parse the condition, if any. + if (PAttr.getNumArgs() == 1) { + CondExpr = PAttr.getArgAsExpr(0); + std::optional<FunctionEffectMode> MaybeMode = + S.ActOnEffectExpression(CondExpr, PAttr.getAttrName()->getName()); + if (!MaybeMode) { + PAttr.setInvalid(); + return true; + } + NewMode = *MaybeMode; + if (NewMode != FunctionEffectMode::Dependent) + CondExpr = nullptr; + } else { + NewMode = FunctionEffectMode::True; + } + } else { + // This is the `blocking` or `allocating` attribute. + if (S.CheckAttrNoArgs(PAttr)) { + // The attribute has been marked invalid. + return true; + } + NewMode = FunctionEffectMode::False; + } + + const FunctionEffect::Kind FEKind = + (NewMode == FunctionEffectMode::False) + ? (IsNonBlocking ? FunctionEffect::Kind::Blocking + : FunctionEffect::Kind::Allocating) + : (IsNonBlocking ? FunctionEffect::Kind::NonBlocking + : FunctionEffect::Kind::NonAllocating); + const FunctionEffectWithCondition NewEC{FunctionEffect(FEKind), + EffectConditionExpr(CondExpr)}; + + if (S.diagnoseConflictingFunctionEffect(FPT->getFunctionEffects(), NewEC, + PAttr.getLoc())) { + PAttr.setInvalid(); + return true; + } + + // Add the effect to the FunctionProtoType. + FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo(); + FunctionEffectSet FX(EPI.FunctionEffects); + FunctionEffectSet::Conflicts Errs; + bool Success = FX.insert(NewEC, Errs); + assert(Success && "effect conflicts should have been diagnosed above"); + EPI.FunctionEffects = FunctionEffectsRef(FX); + + QualType NewType = S.Context.getFunctionType(FPT->getReturnType(), + FPT->getParamTypes(), EPI); + QT = Unwrapped.wrap(S, NewType->getAs<FunctionType>()); + return true; +} + static bool checkMutualExclusion(TypeProcessingState &state, const FunctionProtoType::ExtProtoInfo &EPI, ParsedAttr &Attr, @@ -7834,6 +7944,13 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr, return true; } + if (attr.getKind() == ParsedAttr::AT_NonBlocking || + attr.getKind() == ParsedAttr::AT_NonAllocating || + attr.getKind() == ParsedAttr::AT_Blocking || + attr.getKind() == ParsedAttr::AT_Allocating) { + return handleNonBlockingNonAllocatingTypeAttr(state, attr, type, unwrapped); + } + // Delay if the type didn't work out to a function. if (!unwrapped.isFunctionType()) return false; diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index cf4e80399632b..ec678a55b11b7 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -6267,6 +6267,55 @@ QualType TreeTransform<Derived>::TransformFunctionProtoType( EPI.ExtParameterInfos = nullptr; } + // Transform any function effects with unevaluated conditions. + // Hold this set in a local for the rest of this function, since EPI + // may need to hold a FunctionEffectsRef pointing into it. + std::optional<FunctionEffectSet> NewFX; + if (ArrayRef FXConds = EPI.FunctionEffects.conditions(); !FXConds.empty()) { + NewFX.emplace(); + EnterExpressionEvaluationContext Unevaluated( + getSema(), Sema::ExpressionEvaluationContext::ConstantEvaluated); + + for (const FunctionEffectWithCondition &PrevEC : EPI.FunctionEffects) { + FunctionEffectWithCondition NewEC = PrevEC; + if (Expr *CondExpr = PrevEC.Cond.getCondition()) { + ExprResult NewExpr = getDerived().TransformExpr(CondExpr); + if (NewExpr.isInvalid()) + return QualType(); + std::optional<FunctionEffectMode> Mode = + SemaRef.ActOnEffectExpression(NewExpr.get(), PrevEC.Effect.name()); + if (!Mode) + return QualType(); + + // The condition expression has been transformed, and re-evaluated. + // It may or may not have become constant. + switch (*Mode) { + case FunctionEffectMode::True: + NewEC.Cond = {}; + break; + case FunctionEffectMode::False: + NewEC.Effect = FunctionEffect(PrevEC.Effect.oppositeKind()); + NewEC.Cond = {}; + break; + case FunctionEffectMode::Dependent: + NewEC.Cond = EffectConditionExpr(NewExpr.get()); + break; + case FunctionEffectMode::None: + llvm_unreachable( + "FunctionEffectMode::None shouldn't be possible here"); + } + } + if (!SemaRef.diagnoseConflictingFunctionEffect(*NewFX, NewEC, + TL.getBeginLoc())) { + FunctionEffectSet::Conflicts Errs; + NewFX->insert(NewEC, Errs); + assert(Errs.empty()); + } + } + EPI.FunctionEffects = *NewFX; + EPIChanged = true; + } + QualType Result = TL.getType(); if (getDerived().AlwaysRebuild() || ResultType != T->getReturnType() || T->getParamTypes() != llvm::ArrayRef(ParamTypes) || EPIChanged) { diff --git a/clang/test/Sema/attr-nonblocking-sema.c b/clang/test/Sema/attr-nonblocking-sema.c new file mode 100644 index 0000000000000..0647e47febef2 --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-sema.c @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 %s + +// Tests for a few cases involving C functions without prototypes. + +void noproto() __attribute__((nonblocking)) // expected-error {{'nonblocking' function must have a prototype}} +{ +} + +// This will succeed +void noproto(void) __attribute__((blocking)); + +// A redeclaration isn't any diff erent - a prototype is required. +void f1(void); +void f1() __attribute__((nonblocking)); // expected-error {{'nonblocking' function must have a prototype}} diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp new file mode 100644 index 0000000000000..38bf2ac8f8a4c --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-sema.cpp @@ -0,0 +1,183 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s + +#if !__has_attribute(nonblocking) +#error "the 'nonblocking' attribute is not available" +#endif + +// --- ATTRIBUTE SYNTAX: SUBJECTS --- + +int nl_var [[clang::nonblocking]]; // expected-warning {{'nonblocking' only applies to function types; type here is 'int'}} +struct nl_struct {} [[clang::nonblocking]]; // expected-warning {{attribute 'nonblocking' is ignored, place it after "struct" to apply attribute to type declaration}} +struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' attribute cannot be applied to a declaration}} + +// Positive case +typedef void (*fo)() [[clang::nonblocking]]; +void (*read_me_and_weep( + int val, void (*func)(int) [[clang::nonblocking]]) + [[clang::nonblocking]]) (int) + [[clang::nonblocking]]; + +// --- ATTRIBUTE SYNTAX: ARGUMENT COUNT --- +void nargs_1() [[clang::nonblocking(1, 2)]]; // expected-error {{'nonblocking' attribute takes no more than 1 argument}} +void nargs_2() [[clang::nonallocating(1, 2)]]; // expected-error {{'nonallocating' attribute takes no more than 1 argument}} +void nargs_3() [[clang::blocking(1)]]; // expected-error {{'blocking' attribute takes no arguments}} +void nargs_4() [[clang::allocating(1)]]; // expected-error {{'allocating' attribute takes no arguments}} + +// --- ATTRIBUTE SYNTAX: COMBINATIONS --- +// Check invalid combinations of nonblocking/nonallocating attributes + +void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{'blocking' and 'nonblocking' attributes are not compatible}} +void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{'nonblocking' and 'blocking' attributes are not compatible}} + +void nl_true_false_3() [[clang::nonblocking, clang::blocking]]; // expected-error {{'blocking' and 'nonblocking' attributes are not compatible}} +void nl_true_false_4() [[clang::blocking, clang::nonblocking]]; // expected-error {{'nonblocking' and 'blocking' attributes are not compatible}} + +void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonallocating' attributes are not compatible}} +void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{'nonallocating' and 'allocating' attributes are not compatible}} + +void na_true_false_3() [[clang::nonallocating, clang::allocating]]; // expected-error {{'allocating' and 'nonallocating' attributes are not compatible}} +void na_true_false_4() [[clang::allocating, clang::nonallocating]]; // expected-error {{'nonallocating' and 'allocating' attributes are not compatible}} + +void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]]; +void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]]; + +void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonblocking' attributes are not compatible}} +void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{'nonblocking' and 'allocating' attributes are not compatible}} + +void nl_false_na_true_1() [[clang::blocking]] [[clang::nonallocating]]; +void nl_false_na_true_2() [[clang::nonallocating]] [[clang::blocking]]; + +void nl_false_na_false_1() [[clang::blocking]] [[clang::allocating]]; +void nl_false_na_false_2() [[clang::allocating]] [[clang::blocking]]; + +// --- TYPE CONVERSIONS --- + +void unannotated(); +void nonblocking() [[clang::nonblocking]]; +void nonallocating() [[clang::nonallocating]]; +void type_conversions() +{ + // It's fine to remove a performance constraint. + void (*fp_plain)(); + + fp_plain = nullptr; + fp_plain = unannotated; + fp_plain = nonblocking; + fp_plain = nonallocating; + + // Adding/spoofing nonblocking is unsafe. + void (*fp_nonblocking)() [[clang::nonblocking]]; + fp_nonblocking = nullptr; + fp_nonblocking = nonblocking; + fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + + // Adding/spoofing nonallocating is unsafe. + void (*fp_nonallocating)() [[clang::nonallocating]]; + fp_nonallocating = nullptr; + fp_nonallocating = nonallocating; + fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated; + fp_nonallocating = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}} +} + +#ifdef __cplusplus +struct PTMF { + void unannotated(); + void nonblocking() [[clang::nonblocking]]; + void nonallocating() [[clang::nonallocating]]; +}; + +void type_conversions_ptmf() +{ + // It's fine to remove a performance constraint. + void (PTMF::*ptmf_plain)() = nullptr; + + ptmf_plain = &PTMF::unannotated; + ptmf_plain = &PTMF::nonblocking; + ptmf_plain = &PTMF::nonallocating; + + // Adding/spoofing nonblocking is unsafe. + void (PTMF::*fp_nonblocking)() [[clang::nonblocking]] = nullptr; + fp_nonblocking = &PTMF::nonblocking; + fp_nonblocking = &PTMF::unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + fp_nonblocking = &PTMF::nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + + // Adding/spoofing nonallocating is unsafe. + void (PTMF::*fp_nonallocating)() [[clang::nonallocating]] = nullptr; + fp_nonallocating = &PTMF::nonallocating; + fp_nonallocating = &PTMF::nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated; + fp_nonallocating = &PTMF::unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}} +} + +// There was a bug: noexcept and nonblocking could be individually removed in conversion, but not both +void type_conversions_2() +{ + auto receives_fp = [](void (*fp)()) { + }; + + auto ne = +[]() noexcept {}; + auto nl = +[]() [[clang::nonblocking]] {}; + auto nl_ne = +[]() noexcept [[clang::nonblocking]] {}; + + receives_fp(ne); + receives_fp(nl); + receives_fp(nl_ne); +} +#endif + +// --- VIRTUAL METHODS --- +// Attributes propagate to overridden methods, so no diagnostics except for conflicts. +// Check this in the syntax tests too. +#ifdef __cplusplus +struct Base { + virtual void f1(); + virtual void nonblocking() noexcept [[clang::nonblocking]]; + virtual void nonallocating() noexcept [[clang::nonallocating]]; + virtual void f2() [[clang::nonallocating]]; // expected-note {{previous declaration is here}} +}; + +struct Derived : public Base { + void f1() [[clang::nonblocking]] override; + void nonblocking() noexcept override; + void nonallocating() noexcept override; + void f2() [[clang::allocating]] override; // expected-warning {{effects conflict when merging declarations; kept 'allocating', discarded 'nonallocating'}} +}; +#endif // __cplusplus + +// --- REDECLARATIONS --- + +void f2(); +void f2() [[clang::nonblocking]]; // expected-note {{previous declaration is here}} +void f2(); // expected-warning {{attribute 'nonblocking' on function does not match previous declaration}} +// Note: we verify that the attribute is actually seen during the constraints tests. + +void f3() [[clang::blocking]]; // expected-note {{previous declaration is here}} +void f3() [[clang::nonblocking]]; // expected-warning {{effects conflict when merging declarations; kept 'blocking', discarded 'nonblocking'}} + +// --- OVERLOADS --- +#ifdef __cplusplus +struct S { + void foo(); // expected-note {{previous declaration is here}} + void foo() [[clang::nonblocking]]; // expected-error {{class member cannot be redeclared}} +}; +#endif // __cplusplus + +// --- COMPUTED NONBLOCKING --- +void f4() [[clang::nonblocking(__builtin_memset)]] {} // expected-error {{nonblocking attribute requires an integer constant}} + +#ifdef __cplusplus +// Unexpanded parameter pack +template <bool ...val> +void f5() [[clang::nonblocking(val /* NO ... here */)]] {} // expected-error {{expression contains unexpanded parameter pack 'val'}} + +void f6() { f5<true, false>(); } + +template <bool B> +void ambiguous() [[clang::nonblocking(B)]] [[clang::blocking]]; // expected-note {{candidate template ignored: substitution failure [with B = true]: 'blocking' and 'nonblocking' attributes are not compatible}} + +void f7() { + ambiguous<true>(); // expected-error {{no matching function for call to 'ambiguous'}} + ambiguous<false>(); +} +#endif // __cplusplus diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp new file mode 100644 index 0000000000000..644ed754b04da --- /dev/null +++ b/clang/test/Sema/attr-nonblocking-syntax.cpp @@ -0,0 +1,216 @@ +// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s + +// Make sure that the attribute gets parsed and attached to the correct AST elements. + +#pragma clang diagnostic ignored "-Wunused-variable" + +// ========================================================================================= +// Square brackets, true + +namespace square_brackets { + +// 1. On the type of the FunctionDecl +void nl_function() [[clang::nonblocking]]; +// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))' + +// 2. On the type of the VarDecl holding a function pointer +void (*nl_func_a)() [[clang::nonblocking]]; +// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((nonblocking))' + +// 3. On the type of the ParmVarDecl of a function parameter +static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]); +// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((nonblocking))' + +// 4. As an AttributedType within the nested types of a typedef +typedef void (*nl_fp_type)() [[clang::nonblocking]]; +// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((nonblocking))' +using nl_fp_talias = void (*)() [[clang::nonblocking]]; +// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((nonblocking))' + +// 5. From a typedef or typealias, on a VarDecl +nl_fp_type nl_fp_var1; +// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((nonblocking))' +nl_fp_talias nl_fp_var2; +// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((nonblocking))' + +// 6. On type of a FieldDecl +struct Struct { + void (*nl_func_field)() [[clang::nonblocking]]; +// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((nonblocking))' +}; + +// nonallocating should NOT be subsumed into nonblocking +void nl1() [[clang::nonblocking]] [[clang::nonallocating]]; +// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((nonblocking)) __attribute__((nonallocating))' + +void nl2() [[clang::nonallocating]] [[clang::nonblocking]]; +// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((nonblocking)) __attribute__((nonallocating))' + +decltype(nl1) nl3; +// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((nonblocking)) __attribute__((nonallocating))' + +// Attribute propagates from base class virtual method to overrides. +struct Base { + virtual void nb_method() [[clang::nonblocking]]; +}; +struct Derived : public Base { + void nb_method() override; + // CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((nonblocking))' +}; + +// Dependent expression +template <bool V> +struct Dependent { + void nb_method2() [[clang::nonblocking(V)]]; + // CHECK: CXXMethodDecl {{.*}} nb_method2 'void () __attribute__((nonblocking(V)))' +}; + +// --- Blocks --- + +// On the type of the VarDecl holding a BlockDecl +void (^nl_block1)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] {}; +// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((nonblocking))' + +int (^nl_block2)() [[clang::nonblocking]] = ^() [[clang::nonblocking]] { return 0; }; +// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((nonblocking))' + +// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute +static void blockCaller() { nl_block1(); } +// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((nonblocking))' + +// --- Lambdas --- + +// On the operator() of a lambda's CXXMethodDecl +auto nl_lambda = []() [[clang::nonblocking]] {}; +// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((nonblocking))' inline + +// ========================================================================================= +// Square brackets, false + +void nl_func_false() [[clang::blocking]]; +// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((blocking))' + +auto nl_lambda_false = []() [[clang::blocking]] {}; +// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((blocking))' + +} // namespace square_brackets + +// ========================================================================================= +// GNU-style attribute, true + +namespace gnu_style { + +// 1. On the type of the FunctionDecl +void nl_function() __attribute__((nonblocking)); +// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))' + +// 1a. Alternate placement on the FunctionDecl +__attribute__((nonblocking)) void nl_function(); +// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((nonblocking))' + +// 2. On the type of the VarDecl holding a function pointer +void (*nl_func_a)() __attribute__((nonblocking)); +// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((nonblocking))' + +// 2a. Alternate attribute placement on VarDecl +__attribute__((nonblocking)) void (*nl_func_b)(); +// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((nonblocking))' + +// 3. On the type of the ParmVarDecl of a function parameter +static void nlReceiver(void (*nl_func)() __attribute__((nonblocking))); +// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((nonblocking))' + +// 4. As an AttributedType within the nested types of a typedef +// Note diff erent placement from square brackets for the typealias. +typedef void (*nl_fp_type)() __attribute__((nonblocking)); +// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((nonblocking))' +using nl_fp_talias = __attribute__((nonblocking)) void (*)(); +// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((nonblocking))' + +// 5. From a typedef or typealias, on a VarDecl +nl_fp_type nl_fp_var1; +// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((nonblocking))' +nl_fp_talias nl_fp_var2; +// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((nonblocking))' + +// 6. On type of a FieldDecl +struct Struct { + void (*nl_func_field)() __attribute__((nonblocking)); +// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((nonblocking))' +}; + +} // namespace gnu_style + +// ========================================================================================= +// nonallocating and allocating - quick checks because the code paths are generally +// identical after parsing. + +void na_function() [[clang::nonallocating]]; +// CHECK: FunctionDecl {{.*}} na_function 'void () __attribute__((nonallocating))' + +void na_true_function() [[clang::nonallocating(true)]]; +// CHECK: FunctionDecl {{.*}} na_true_function 'void () __attribute__((nonallocating))' + +void na_false_function() [[clang::nonallocating(false)]]; +// CHECK: FunctionDecl {{.*}} na_false_function 'void () __attribute__((allocating))' + +void alloc_function() [[clang::allocating]]; +// CHECK: FunctionDecl {{.*}} alloc_function 'void () __attribute__((allocating))' + + +// ========================================================================================= +// Non-blocking with an expression parameter + +void t0() [[clang::nonblocking(1 - 1)]]; +// CHECK: FunctionDecl {{.*}} t0 'void () __attribute__((blocking))' +void t1() [[clang::nonblocking(1 + 1)]]; +// CHECK: FunctionDecl {{.*}} t1 'void () __attribute__((nonblocking))' + +template <bool V> +struct ValueDependent { + void nb_method() [[clang::nonblocking(V)]]; +}; + +void t3() [[clang::nonblocking]] +{ + ValueDependent<false> x1; + x1.nb_method(); +// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent +// CHECK: TemplateArgument integral 'false' +// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((blocking))' + + ValueDependent<true> x2; + x2.nb_method(); +// CHECK: ClassTemplateSpecializationDecl {{.*}} ValueDependent +// CHECK: TemplateArgument integral 'true' +// CHECK: CXXMethodDecl {{.*}} nb_method 'void () __attribute__((nonblocking))' +} + +template <typename X> +struct TypeDependent { + void td_method() [[clang::nonblocking(X::is_nb)]]; +}; + +struct NBPolicyTrue { + static constexpr bool is_nb = true; +}; + +struct NBPolicyFalse { + static constexpr bool is_nb = false; +}; + +void t4() +{ + TypeDependent<NBPolicyFalse> x1; + x1.td_method(); +// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent +// CHECK: TemplateArgument type 'NBPolicyFalse' +// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((blocking))' + + TypeDependent<NBPolicyTrue> x2; + x2.td_method(); +// CHECK: ClassTemplateSpecializationDecl {{.*}} TypeDependent +// CHECK: TemplateArgument type 'NBPolicyTrue' +// CHECK: CXXMethodDecl {{.*}} td_method 'void () __attribute__((nonblocking))' +} + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits