[clang] nonblocking/nonallocating attributes (was: nolock/noalloc) (PR #84983)
via cfe-commits
cfe-commits at lists.llvm.org
Sat May 4 10:38:19 PDT 2024
================
@@ -4639,6 +4644,312 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
}
};
+// ------------------------------------------------------------------------------
+
+class Decl;
+class CXXMethodDecl;
+struct FunctionEffectDiff;
+class FunctionEffectsRef;
+class FunctionEffectSet;
+
+/// 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.
+ /// This is only used if the effect has FE_InferrableOnCallees flag set.
+ /// 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 FunctionEffectCondition {
+ Expr *Cond = nullptr; // if null, unconditional
+
+public:
+ FunctionEffectCondition() = default;
+ FunctionEffectCondition(Expr *E) : Cond(E) {} // implicit OK
+
+ Expr *expr() const { return Cond; }
+
+ bool operator==(const FunctionEffectCondition &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;
+ FunctionEffectCondition Cond;
+
+ /// Return a textual description of the effect, and its condition, if any.
+ std::string description() const;
+};
+
+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 differing 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;
+};
+
+/// Support iteration in parallel through a pair of FunctionEffect and
+/// FunctionEffectCondition containers.
+template <typename Container> class FunctionEffectIterator {
+ const Container &Outer;
+ size_t Idx;
+
+public:
+ 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;
+ }
+
+ // prefix increment
+ FunctionEffectIterator operator++() {
+ ++Idx;
+ return *this;
+ }
+
+ FunctionEffectWithCondition operator*() const {
+ const bool HasConds = !Outer.Conditions.empty();
+ return FunctionEffectWithCondition{Outer.Effects[Idx],
+ HasConds ? Outer.Conditions[Idx]
+ : FunctionEffectCondition()};
+ }
+};
+
+/// 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).
+///
+/// Invariant: there is never more than one instance of any given effect.
+class FunctionEffectsRef {
+ ArrayRef<FunctionEffect> Effects;
+
+ // The array of conditions is either empty or has the same size
+ // as the array of effects.
+ ArrayRef<FunctionEffectCondition> Conditions;
+
+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);
+
+ FunctionEffectsRef() = default;
+
+ // The arrays are expected to have been sorted by the caller.
----------------
Sirraide wrote:
> Asserting is easy with `std::is_sorted()`.
Ah yeah; I candidly forgot it existed because I almost never have a reason to use it.
> add FunctionProtoType and FunctionEffectSet as friends, making them both responsible for the invariant.
If that doesn’t make the rest of the code much more complicated then I’d go with that; I’m just worried about a function (in this case a constructor) that has invariants that are supposed to be checked by the caller, but which anyone can just call without checking them.
https://github.com/llvm/llvm-project/pull/84983
More information about the cfe-commits
mailing list