[clang] nonblocking/nonallocating attributes (was: nolock/noalloc) (PR #84983)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 11 14:40:28 PDT 2024


================
@@ -4429,6 +4433,210 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
   }
 };
 
+// ------------------------------------------------------------------------------
+
+class Decl;
+class CXXMethodDecl;
+class FunctionEffectSet;
+
+/// Represents an abstract function effect, using just an enumeration describing
+/// its type. Encapsulates its semantic behaviors.
+class FunctionEffect {
+public:
+  /// Identifies the particular effect.
+  enum class Kind : uint8_t {
+    None,
+    NonBlocking,
+    NonAllocating,
+  };
+
+  /// 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)
+    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
+  };
+
+  /// Describes the result of effects differing between a base class's virtual
+  /// method and an overriding method in a subclass.
+  enum class OverrideResult {
+    Ignore,
+    Warn,
+    Merge // Base method's effects are merged with those of the override.
+  };
+
+private:
+  Kind FKind;
+
+  // 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(Kind::None) {}
+
+  explicit FunctionEffect(Kind T) : FKind(T) {}
+
+  /// The kind of the effect.
+  Kind kind() const { return FKind; }
+
+  /// Return an opaque integer, as a serializable representation.
+  uint32_t getAsOpaqueValue() const { return llvm::to_underlying(FKind); }
+
+  /// Construct from a serialized representation.
+  static FunctionEffect getFromOpaqueValue(uint32_t V) {
+    return FunctionEffect(static_cast<Kind>(V));
+  }
+
+  /// Flags describing some behaviors of the effect.
+  Flags flags() const {
+    switch (FKind) {
+    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::None:
+      break;
+    }
+    llvm_unreachable("unknown effect kind");
+  }
+
+  /// The description printed in diagnostics, e.g. 'nonblocking'.
+  StringRef name() const;
+
+  /// Return true if adding or removing the effect as part of a type conversion
+  /// should generate a diagnostic.
+  bool shouldDiagnoseConversion(bool Adding, QualType OldType,
+                                const FunctionEffectSet &OldFX,
+                                QualType NewType,
+                                const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
+                                   const FunctionEffectSet &OldFX,
+                                   const FunctionDecl &NewFunction,
+                                   const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method
+  /// override should generate a diagnostic.
+  OverrideResult
+  shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+                               const FunctionEffectSet &OldFX,
+                               const CXXMethodDecl &NewMethod,
+                               const FunctionEffectSet &NewFX) 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,
+                                  const FunctionEffectSet &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.FKind != RHS.FKind;
+  }
+  friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+    return LHS.FKind < RHS.FKind;
+  }
+};
+
+/// A value type, representing a set of FunctionEffects. To reduce superfluous
+/// retain/release, however, prefer to pass by reference.
+class FunctionEffectSet {
----------------
Sirraide wrote:

> I'm trying to decide whether `FunctionEffectSet` should hold the "computed effects" too, or whether `FunctionProtoType` should hold that separately.

If it keeps the `FunctionProtoType` smaller/simpler then I’d put it in the `FunctionEffectSet`, otherwise I don’t think it matters too much.

> In any case, something other than `FunctionProtoType` needs to handle merging the computed effects (once their expressions are non-dependent), because that operation would use `Sema::PerformContextuallyConvertToBool()`, 

Yeah, typically Sema creates the AST nodes, so it makes sense to put e.g. `MergeFunctionEffects` or whatever in Sema.

> and `FunctionProtoType` doesn't have a `Sema&` or precedent for using one.

Yeah, typically we put functions like that in Sema (or in the ASTContext in some cases) and have Sema operate on the types rather than the other way around. AST nodes are mostly ‘data’; the actual ‘logic’ is typically somewhere else, if you will (most of the member functions of AST nodes just deal w/ accessing data/information that’s part of the node).

https://github.com/llvm/llvm-project/pull/84983


More information about the cfe-commits mailing list