[clang] f03cb00 - [Clang] Introduce `nonblocking`/`nonallocating` attributes (#84983)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 24 03:51:36 PDT 2024
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 <dwyatt at apple.com>
Co-authored-by: Shafik Yaghmour <shafik.yaghmour at intel.com>
Co-authored-by: Aaron Ballman <aaron at aaronballman.com>
Co-authored-by: Sirraide <aeternalmail at 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))'
+}
+
More information about the cfe-commits
mailing list