[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-30 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;

dougsonos wrote:

Actually I'm not sure (2) is possible as described; it would require some UB to 
have a union of a pointer with a struct. One more idea:

4. Make `TinyFunctionEffectSet` as in (3), maybe called 
`BaseFunctionEffectSet`. Eliminate code duplication by making 
`FunctionEffectSet` combine `BaseFunctionEffectSet` with the vector of 
`EffectConditionExpr`.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-30 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-30 Thread Doug Wyatt via cfe-commits


@@ -10897,6 +10902,53 @@ Attr 
*Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
   return nullptr;
 }
 
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+   const FunctionEffectsRef ) {
+  if (!D->hasBody()) {
+if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody())
+  return;
+  }
+
+  if (Diags.getIgnoreAllWarnings() ||
+  (Diags.getSuppressSystemWarnings() &&
+   SourceMgr.isInSystemHeader(D->getLocation(
+return;
+
+  if (hasUncompilableErrorOccurred())
+return;

dougsonos wrote:

Well, I extracted that function, searched for users of 
`hasUncompilableErrorOccurred` and discovered that they are all subtly 
different. The closest candidate in AnalysisBasedWarnings has this extra twist 
on the pattern:

```
  if (S.hasUncompilableErrorOccurred()) {
// Flush out any possibly unreachable diagnostics.
flushDiagnostics(S, fscope);
return;
  }
```

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-30 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;

dougsonos wrote:

To summarize the multiple data structures and explore alternatives:

`FunctionEffectsRef` is a pair of `ArrayRef`s, one of `FunctionEffect`, another 
of `EffectConditionExpr`. These are convenient and normalized forms of what is 
held in `FunctionProtoType`'s trailing objects.

`FunctionEffectSet` is a pair of small vectors, also `FunctionEffect` and 
`EffectConditionExpr`. To interoperate with `FunctionEffectsRef`, the vectors 
are not "interleaved".

Once we get to this secondary analysis, none of the effects have conditions any 
more, and we need to hold potentially thousands of little sets to indicate 
which effects are either declared on, or inferred on, every visited `Decl`. 
Here are some possible representations for these sets of effects without 
conditions:

1. Use `FunctionEffectSet` and don't worry that it's 32 bytes where 8 (or less) 
would have sufficed.

2. Use low-level layout tricks to make `FunctionEffectSet` 8 bytes in the 
overwhelmingly common case where no effects have conditions. (e.g. it's either 
a pointer to two SmallVectors, or a uint8_t count and an array of 7 
FunctionEffects, determined by one bit.)

3. Make a `TinyFunctionEffectSet` which holds only effects, no conditions. 
Using a bit vector (of effect kinds) would be possible but make it difficult to 
build the `ArrayRef` needed by some methods of 
`FunctionEffect`. Thus the current array-like implementation here in the PR.

Options 1 and 2 are appealing in that FunctionEffectSet already implements the 
iteration, insertion and set difference options -- albeit with some extra 
business about conflicts that shouldn't happen in the 2nd phase analysis. The 
more I tinker with this third class, the more I appreciate your sense that it's 
redundant. Then it becomes a tradeoff 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-27 Thread Doug Wyatt via cfe-commits


@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t FKind : 3;

dougsonos wrote:

That PR has been merged.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -10897,6 +10902,53 @@ Attr 
*Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
   return nullptr;
 }
 
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+   const FunctionEffectsRef ) {
+  if (!D->hasBody()) {
+if (const auto *FD = D->getAsFunction(); FD && !FD->willHaveBody())
+  return;
+  }
+
+  if (Diags.getIgnoreAllWarnings() ||
+  (Diags.getSuppressSystemWarnings() &&
+   SourceMgr.isInSystemHeader(D->getLocation(
+return;
+
+  if (hasUncompilableErrorOccurred())
+return;

dougsonos wrote:

Something like this as a new method of `Sema`:

`bool shouldSkipComplexAnalysis(SourceLocation SL);`

?

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;
+
+public:
+  EffectSet() = default;
+  explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+  operator ArrayRef() const {
+return ArrayRef(Impl.cbegin(), Impl.cend());
+  }
+
+  using iterator = const FunctionEffect *;
+  iterator begin() const { return Impl.cbegin(); }
+  iterator end() const { return Impl.cend(); }
+
+  void insert(const FunctionEffect ) {
+FunctionEffect *Iter = Impl.begin();
+FunctionEffect *End = Impl.end();
+// linear search; lower_bound is overkill for a tiny vector like this
+for (; Iter != End; ++Iter) {
+  if (*Iter == Effect)
+return;
+  if (Effect < *Iter)
+break;
+}
+Impl.insert(Iter, Effect);
+  }
+  void insert(const EffectSet ) {
+for (const FunctionEffect  : Set) {
+  // push_back because set is already sorted
+  Impl.push_back(Item);
+}
+  }
+  void insert(FunctionEffectsRef FX) {
+for (const FunctionEffectWithCondition  : FX) {
+  assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+  // push_back because set is already sorted
+  Impl.push_back(EC.Effect);
+}
+  }
+  bool contains(const FunctionEffect::Kind EK) const {
+for (const FunctionEffect  : Impl)
+  if (E.kind() == EK)
+return true;
+return false;
+  }
+
+  void dump(llvm::raw_ostream ) const;
+
+  static EffectSet difference(ArrayRef LHS,
+  ArrayRef RHS) {
+EffectSet Result;
+std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+std::back_inserter(Result.Impl));
+return Result;
+  }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream ) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const FunctionEffect  : *this) {
+if (!First)
+  OS 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -873,30 +873,17 @@ class Sema final : public SemaBase {
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
 
-  // - function effects ---
+  /// All functions/lambdas/blocks which have bodies and which have a non-empty
+  /// FunctionEffectsRef to be verified.
+  SmallVector DeclsWithEffectsToVerify;
+  /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
+  /// are all null.
+  FunctionEffectSet AllEffectsToVerify;

dougsonos wrote:

Well, the large codebases I've adapted care deeply about `nonblocking` and do 
not use `nonallocating`. Those two are very closely related, however, so I 
think your point stands.

There is some set logic which I would have to revisit -- there's a need to 
know, for every visited function, which effects are being explicitly verified 
vs. which effects are being checked for inferability...

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;
+
+public:
+  EffectSet() = default;
+  explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+  operator ArrayRef() const {
+return ArrayRef(Impl.cbegin(), Impl.cend());
+  }
+
+  using iterator = const FunctionEffect *;
+  iterator begin() const { return Impl.cbegin(); }
+  iterator end() const { return Impl.cend(); }
+
+  void insert(const FunctionEffect ) {
+FunctionEffect *Iter = Impl.begin();
+FunctionEffect *End = Impl.end();
+// linear search; lower_bound is overkill for a tiny vector like this
+for (; Iter != End; ++Iter) {
+  if (*Iter == Effect)
+return;
+  if (Effect < *Iter)
+break;
+}
+Impl.insert(Iter, Effect);
+  }
+  void insert(const EffectSet ) {
+for (const FunctionEffect  : Set) {
+  // push_back because set is already sorted
+  Impl.push_back(Item);
+}
+  }
+  void insert(FunctionEffectsRef FX) {
+for (const FunctionEffectWithCondition  : FX) {
+  assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+  // push_back because set is already sorted
+  Impl.push_back(EC.Effect);
+}
+  }
+  bool contains(const FunctionEffect::Kind EK) const {
+for (const FunctionEffect  : Impl)
+  if (E.kind() == EK)
+return true;
+return false;
+  }
+
+  void dump(llvm::raw_ostream ) const;
+
+  static EffectSet difference(ArrayRef LHS,
+  ArrayRef RHS) {
+EffectSet Result;
+std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+std::back_inserter(Result.Impl));
+return Result;
+  }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream ) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const FunctionEffect  : *this) {
+if (!First)
+  OS 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {

dougsonos wrote:

Yeah. If `isTrivial()` then it's verifiable else it just depends on `hasBody()`.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {

dougsonos wrote:

But possibly the subsequent check of `isTrivial()` would work if it were 
first...

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {

dougsonos wrote:

That's what I had hoped when I first wrote this, and then I discovered (and 
re-discovered just now, with some debugging code) that there are many methods 
which are considered inlined but without bodies. My crude logging says that 
they are implicit constructors and destructors, e.g. `~atomic` `~__atomic_base` 
`__tuple_indices` `__tuple_types` etc. etc.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;
+
+public:
+  EffectSet() = default;
+  explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+  operator ArrayRef() const {
+return ArrayRef(Impl.cbegin(), Impl.cend());
+  }
+
+  using iterator = const FunctionEffect *;
+  iterator begin() const { return Impl.cbegin(); }
+  iterator end() const { return Impl.cend(); }
+
+  void insert(const FunctionEffect ) {
+FunctionEffect *Iter = Impl.begin();
+FunctionEffect *End = Impl.end();
+// linear search; lower_bound is overkill for a tiny vector like this
+for (; Iter != End; ++Iter) {
+  if (*Iter == Effect)
+return;
+  if (Effect < *Iter)
+break;
+}
+Impl.insert(Iter, Effect);
+  }
+  void insert(const EffectSet ) {
+for (const FunctionEffect  : Set) {
+  // push_back because set is already sorted
+  Impl.push_back(Item);
+}
+  }
+  void insert(FunctionEffectsRef FX) {
+for (const FunctionEffectWithCondition  : FX) {
+  assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+  // push_back because set is already sorted
+  Impl.push_back(EC.Effect);
+}
+  }
+  bool contains(const FunctionEffect::Kind EK) const {
+for (const FunctionEffect  : Impl)
+  if (E.kind() == EK)
+return true;
+return false;
+  }
+
+  void dump(llvm::raw_ostream ) const;
+
+  static EffectSet difference(ArrayRef LHS,
+  ArrayRef RHS) {
+EffectSet Result;
+std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+std::back_inserter(Result.Impl));
+return Result;
+  }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream ) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const FunctionEffect  : *this) {
+if (!First)
+  OS 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -15609,6 +15661,10 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope 
*FnBodyScope, Decl *D,
   getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
 Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
+  if (Context.hasAnyFunctionEffects())
+if (const auto FX = FD->getFunctionEffects(); !FX.empty())
+  maybeAddDeclWithEffects(FD, FX);

dougsonos wrote:

Not sure if you saw the performance PR where `hasAnyFunctionEffects()` was 
introduced but it's left me a little paranoid about even just the function call 
overhead. Possibly a good compromise would be to make `maybeAddDeclWithEffects` 
inline.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [NFC] Reduce size of FunctionEffect to 1 byte. (PR #100753)

2024-07-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos updated 
https://github.com/llvm/llvm-project/pull/100753

>From 3cc18ded835f3a25dc98bc81988d3a2f1181c300 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 1/2] Reduce size of FunctionEffect to 1 byte.

---
 clang/include/clang/AST/Type.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..cb345c3028909 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,16 +4699,16 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t 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)) {}
+  FunctionEffect() : FKind(uint8_t(Kind::None)) {}
 
-  explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {}
+  explicit FunctionEffect(Kind K) : FKind(uint8_t(K)) {}
 
   /// The kind of the effect.
   Kind kind() const { return Kind(FKind); }

>From 2021255f055106effabff218149a895168031c6c Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Fri, 26 Jul 2024 07:46:47 -0700
Subject: [PATCH 2/2] Make FunctionEffect just have a Kind instead of a
 bitfield

---
 clang/include/clang/AST/Type.h | 11 +--
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index cb345c3028909..509f0d95643b0 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4698,26 +4698,25 @@ class FunctionEffect {
   };
 
 private:
-  LLVM_PREFERRED_TYPE(Kind)
-  uint8_t FKind : 3;
+  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(uint8_t(Kind::None)) {}
+  FunctionEffect() : FKind(Kind::None) {}
 
-  explicit FunctionEffect(Kind K) : FKind(uint8_t(K)) {}
+  explicit FunctionEffect(Kind K) : FKind(K) {}
 
   /// The kind of the effect.
-  Kind kind() const { return Kind(FKind); }
+  Kind kind() const { return FKind; }
 
   /// Return the opposite kind, for effects which have opposites.
   Kind oppositeKind() const;
 
   /// For serialization.
-  uint32_t toOpaqueInt32() const { return FKind; }
+  uint32_t toOpaqueInt32() const { return uint32_t(FKind); }
   static FunctionEffect fromOpaqueInt32(uint32_t Value) {
 return FunctionEffect(Kind(Value));
   }

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-26 Thread Doug Wyatt via cfe-commits


@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t FKind : 3;

dougsonos wrote:

Opened PR https://github.com/llvm/llvm-project/pull/100753 for this.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [NFC] Reduce size of FunctionEffect to 1 byte. (PR #100753)

2024-07-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos created 
https://github.com/llvm/llvm-project/pull/100753

As a preliminary to PR https://github.com/llvm/llvm-project/pull/99656 , reduce 
the size of `FunctionEffect` to 1 byte.

>From 3cc18ded835f3a25dc98bc81988d3a2f1181c300 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH] Reduce size of FunctionEffect to 1 byte.

---
 clang/include/clang/AST/Type.h | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..cb345c3028909 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,16 +4699,16 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t 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)) {}
+  FunctionEffect() : FKind(uint8_t(Kind::None)) {}
 
-  explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {}
+  explicit FunctionEffect(Kind K) : FKind(uint8_t(K)) {}
 
   /// The kind of the effect.
   Kind kind() const { return Kind(FKind); }

___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+@interface OCClass
+- (void)method;
+@end
+
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+   [oc method]; // expected-warning {{'nonblocking' function must not 
access an ObjC method or property}}
+}

dougsonos wrote:

This looks like a bit of a job of its own so I wrote
https://github.com/llvm/llvm-project/issues/100676

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t FKind : 3;

dougsonos wrote:

(sorry, what's "NFC" in this context?)

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+@interface OCClass
+- (void)method;
+@end
+
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+   [oc method]; // expected-warning {{'nonblocking' function must not 
access an ObjC method or property}}
+}

dougsonos wrote:

Yeah... This gets into territory I hadn't ventured into yet, since `super` 
can't appear outside of the context of an ObjC method (right?), and currently, 
`nonblocking` and `nonallocating` are ignored on ObjC methods. The rationale 
was that you have to bend over backwards to bypass the ObjC runtime and call a 
method safely, and once you're in a method, you can't do much at all safely, so 
is it really worth the trouble?

So maybe the first question is how to fix this bug -- the following generates 
no diagnostics:

```
@implementation Derived
- (void)problem __attribute((nonblocking)) {
throw 42;
}
@end
```

The easiest solution is to forbid the attribute in this context.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) 
currently prevent
+// the AnalysisBasedWarnings pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nl1() [[clang::nonblocking]]
+{
+   auto* pInt = new int; // expected-warning {{'nonblocking' function must 
not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nonblocking]]
+{
+   static int global; // expected-warning {{'nonblocking' function must 
not have static locals}}
+}
+
+void nl3() [[clang::nonblocking]]
+{
+   try {
+   throw 42; // expected-warning {{'nonblocking' function must not 
throw or catch exceptions}}
+   }
+   catch (...) { // expected-warning {{'nonblocking' function must not 
throw or catch exceptions}}

dougsonos wrote:

Thanks, added checks for `@try`/`@catch`.

As with `malloc` etc., `objc_msgSend` is considered unsafe because it is 
unannotated (and should not be annotated).

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) 
currently prevent
+// the AnalysisBasedWarnings pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nl1() [[clang::nonblocking]]
+{
+   auto* pInt = new int; // expected-warning {{'nonblocking' function must 
not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nonblocking]]
+{
+   static int global; // expected-warning {{'nonblocking' function must 
not have static locals}}
+}
+
+void nl3() [[clang::nonblocking]]
+{
+   try {
+   throw 42; // expected-warning {{'nonblocking' function must not 
throw or catch exceptions}}
+   }
+   catch (...) { // expected-warning {{'nonblocking' function must not 
throw or catch exceptions}}
+   }
+}
+
+void nl4_inline() {}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 
'nonblocking' because it has no definition in this translation unit}}
+
+void nl4() [[clang::nonblocking]]
+{
+   nl4_inline(); // OK
+   nl4_not_inline(); // expected-warning {{'nonblocking' function must not 
call non-'nonblocking' function}}
+}
+
+
+struct HasVirtual {
+   virtual void unsafe(); // expected-note {{virtual method cannot be 
inferred 'nonblocking'}}
+};
+
+void nl5() [[clang::nonblocking]]
+{
+   HasVirtual hv;
+   hv.unsafe(); // expected-warning {{'nonblocking' function must not call 
non-'nonblocking' function}}
+}
+
+void nl6_unsafe(); // expected-note {{function cannot be inferred 
'nonblocking' because it has no definition in this translation unit}}
+void nl6_transitively_unsafe()
+{
+   nl6_unsafe(); // expected-note {{function cannot be inferred 
'nonblocking' because it calls non-'nonblocking' function}}

dougsonos wrote:

Yeah, it used to do that, but in the earlier PR I was convinced that this was 
inconsistent with other diagnostics. 
https://github.com/llvm/llvm-project/pull/84983/files/eb204dff79817667c65f09bbd38ece245f50b99e#r1523330556

In testing, I found that the diagnostics as is were quite usable in an IDE, 
since the warning points right to the caller, and in almost all cases there is 
a note pointing to the callee.

Also, there's a question of how to name a lambda.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-25 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) 
currently prevent
+// the AnalysisBasedWarnings pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nl1() [[clang::nonblocking]]
+{
+   auto* pInt = new int; // expected-warning {{'nonblocking' function must 
not allocate or deallocate memory}}
+}

dougsonos wrote:

The surprising thing about function calls is that all of those functions are 
assumed to be unsafe because they aren't declared `nonblocking` or 
`nonallocating`. So an attempt to use any of those will result in the 
diagnostic:

'nonblocking' function must not call non-'nonblocking' function

A nice thing about this is that there's no need to maintain allow/deny lists.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-20 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-20 Thread Doug Wyatt via cfe-commits


@@ -891,6 +898,12 @@ class Sema final : public SemaBase {
SourceLocation NewLoc,
SourceLocation OldLoc);
 
+  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+  void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef );

dougsonos wrote:

Thanks, did this, including moving some of the other recent, related additions.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-20 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos updated 
https://github.com/llvm/llvm-project/pull/99656

>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 1/3] nonblocking/nonallocating attributes: 2nd pass
 caller/callee analysis/verification

---
 clang/include/clang/AST/Type.h|2 +-
 clang/include/clang/Basic/DiagnosticGroups.td |1 +
 .../clang/Basic/DiagnosticSemaKinds.td|   49 +
 clang/include/clang/Sema/Sema.h   |   13 +
 .../include/clang/Serialization/ASTBitCodes.h |4 +
 clang/include/clang/Serialization/ASTReader.h |3 +
 clang/include/clang/Serialization/ASTWriter.h |1 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp  | 1259 +
 clang/lib/Sema/SemaDecl.cpp   |   56 +
 clang/lib/Sema/SemaExpr.cpp   |4 +
 clang/lib/Sema/SemaLambda.cpp |5 +
 clang/lib/Serialization/ASTReader.cpp |   19 +
 clang/lib/Serialization/ASTWriter.cpp |   12 +
 .../Sema/attr-nonblocking-constraints.cpp |  194 +++
 clang/test/Sema/attr-nonblocking-syntax.cpp   |1 +
 .../attr-nonblocking-constraints.mm   |   23 +
 16 files changed, 1645 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
 create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..08141f75de8db 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t 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
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e043349..55d9442a939da 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", 
[UnsafeBufferUsageInCon
 // Warnings and notes related to the function effects system underlying
 // the nonblocking and nonallocating attributes.
 def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : 
DiagGroup<"perf-constraint-implies-noexcept">;
 
 // 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 d60f32674ca3a..ec02c02d158c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup>;
 
 // Function effects
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,
+  InGroup;
+def note_func_effect_allocates : Note<
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function must not throw or catch exceptions">,
+  InGroup;
+def note_func_effect_throws_or_catches : Note<
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function must not have static locals">,
+  InGroup;
+def note_func_effect_has_static_local : Note<
+  "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function must not use thread-local variables">,
+  InGroup;
+def note_func_effect_uses_thread_local : Note<
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function must not access an ObjC method or property">,
+  InGroup;
+def note_func_effect_calls_objc : Note<
+  "function cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "'%0' function must not call non-'%0' function '%1'">,
+  InGroup;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "'%0' function must not call non-'%0' expression">,
+  InGroup;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+def note_func_effect_call_extern : Note<
+  "function cannot be inferred '%0' because it has no definition in this 
translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+  "function does not permit inference of '%0'">;
+def note_func_effect_call_virtual : Note<
+  "virtual method cannot be inferred '%0'">;
+def note_func_effect_call_func_ptr : Note<
+ 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-20 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos updated 
https://github.com/llvm/llvm-project/pull/99656

>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 1/2] nonblocking/nonallocating attributes: 2nd pass
 caller/callee analysis/verification

---
 clang/include/clang/AST/Type.h|2 +-
 clang/include/clang/Basic/DiagnosticGroups.td |1 +
 .../clang/Basic/DiagnosticSemaKinds.td|   49 +
 clang/include/clang/Sema/Sema.h   |   13 +
 .../include/clang/Serialization/ASTBitCodes.h |4 +
 clang/include/clang/Serialization/ASTReader.h |3 +
 clang/include/clang/Serialization/ASTWriter.h |1 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp  | 1259 +
 clang/lib/Sema/SemaDecl.cpp   |   56 +
 clang/lib/Sema/SemaExpr.cpp   |4 +
 clang/lib/Sema/SemaLambda.cpp |5 +
 clang/lib/Serialization/ASTReader.cpp |   19 +
 clang/lib/Serialization/ASTWriter.cpp |   12 +
 .../Sema/attr-nonblocking-constraints.cpp |  194 +++
 clang/test/Sema/attr-nonblocking-syntax.cpp   |1 +
 .../attr-nonblocking-constraints.mm   |   23 +
 16 files changed, 1645 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
 create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..08141f75de8db 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t 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
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e043349..55d9442a939da 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", 
[UnsafeBufferUsageInCon
 // Warnings and notes related to the function effects system underlying
 // the nonblocking and nonallocating attributes.
 def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : 
DiagGroup<"perf-constraint-implies-noexcept">;
 
 // 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 d60f32674ca3a..ec02c02d158c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup>;
 
 // Function effects
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,
+  InGroup;
+def note_func_effect_allocates : Note<
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function must not throw or catch exceptions">,
+  InGroup;
+def note_func_effect_throws_or_catches : Note<
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function must not have static locals">,
+  InGroup;
+def note_func_effect_has_static_local : Note<
+  "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function must not use thread-local variables">,
+  InGroup;
+def note_func_effect_uses_thread_local : Note<
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function must not access an ObjC method or property">,
+  InGroup;
+def note_func_effect_calls_objc : Note<
+  "function cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "'%0' function must not call non-'%0' function '%1'">,
+  InGroup;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "'%0' function must not call non-'%0' expression">,
+  InGroup;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+def note_func_effect_call_extern : Note<
+  "function cannot be inferred '%0' because it has no definition in this 
translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+  "function does not permit inference of '%0'">;
+def note_func_effect_call_virtual : Note<
+  "virtual method cannot be inferred '%0'">;
+def note_func_effect_call_func_ptr : Note<
+ 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-19 Thread Doug Wyatt via cfe-commits


@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, potentially for the entire duration of the
+// analysis phase, in order to refer to it when explaining why a caller has 
been
+// made unsafe by a callee.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect , DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // unknown: probably function pointer
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (!(FD->hasBody() || FD->isInlined())) {
+// externally defined; we couldn't verify if we wanted to.
+return false;
+  }
+  if (FD->isTrivial()) {
+// Otherwise `struct x { int a; };` would have an unverifiable default
+// constructor.
+return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more 
functions
+  // than have declared effects.
+
+  template  struct FixedVector {
+SizeT Count = 0;
+T Items[Capacity] = {};
+
+using value_type = T;
+
+using iterator = T *;
+using const_iterator = const T *;
+iterator begin() { return [0]; }
+iterator end() { return [Count]; }
+const_iterator begin() const { return [0]; }
+const_iterator end() const { return [Count]; }
+const_iterator cbegin() const { return [0]; }
+const_iterator cend() const { return [Count]; }
+
+void insert(iterator I, const T ) {
+  assert(Count < Capacity);
+  iterator E = end();
+  if (I != E)
+std::copy_backward(I, E, E + 1);
+  *I = Value;
+  ++Count;
+}
+
+void push_back(const T ) {
+  assert(Count < Capacity);
+  Items[Count++] = Value;
+}
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector Impl;
+
+public:
+  EffectSet() = default;
+  explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+  operator ArrayRef() const {
+return ArrayRef(Impl.cbegin(), Impl.cend());
+  }
+
+  using iterator = const FunctionEffect *;
+  iterator begin() const { return Impl.cbegin(); }
+  iterator end() const { return Impl.cend(); }
+
+  void insert(const FunctionEffect ) {
+FunctionEffect *Iter = Impl.begin();
+FunctionEffect *End = Impl.end();
+// linear search; lower_bound is overkill for a tiny vector like this
+for (; Iter != End; ++Iter) {
+  if (*Iter == Effect)
+return;
+  if (Effect < *Iter)
+break;
+}
+Impl.insert(Iter, Effect);
+  }
+  void insert(const EffectSet ) {
+for (const FunctionEffect  : Set) {
+  // push_back because set is already sorted
+  Impl.push_back(Item);
+}
+  }
+  void insert(FunctionEffectsRef FX) {
+for (const FunctionEffectWithCondition  : FX) {
+  assert(EC.Cond.getCondition() ==
+ nullptr); // should be resolved by now, right?
+  // push_back because set is already sorted
+  Impl.push_back(EC.Effect);
+}
+  }
+  bool contains(const FunctionEffect::Kind EK) const {
+for (const FunctionEffect  : Impl)
+  if (E.kind() == EK)
+return true;
+return false;
+  }
+
+  void dump(llvm::raw_ostream ) const;
+
+  static EffectSet difference(ArrayRef LHS,
+  ArrayRef RHS) {
+EffectSet Result;
+std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+std::back_inserter(Result.Impl));
+return Result;
+  }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream ) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const FunctionEffect  : *this) {
+if (!First)
+  OS 

[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-19 Thread Doug Wyatt via cfe-commits


@@ -2551,6 +3807,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
SourceLocation())) {
 CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
   }
+
+  if (S.Context.hasAnyFunctionEffects())

dougsonos wrote:

This doesn't necessarily need to be here in AnalysisBasedWarnings (though it 
does borrow a utility function `isNoexcept()`). It's just convenient because 
this is the right general time, at the end of Sema, to do this check.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-19 Thread Doug Wyatt via cfe-commits


@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup>;
 
 // Function effects
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,
+  InGroup;
+def note_func_effect_allocates : Note<
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function must not throw or catch exceptions">,
+  InGroup;
+def note_func_effect_throws_or_catches : Note<
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function must not have static locals">,
+  InGroup;
+def note_func_effect_has_static_local : Note<
+  "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function must not use thread-local variables">,
+  InGroup;
+def note_func_effect_uses_thread_local : Note<
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function must not access an ObjC method or property">,
+  InGroup;
+def note_func_effect_calls_objc : Note<
+  "function cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "'%0' function must not call non-'%0' function '%1'">,
+  InGroup;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "'%0' function must not call non-'%0' expression">,
+  InGroup;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+def note_func_effect_call_extern : Note<
+  "function cannot be inferred '%0' because it has no definition in this 
translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+  "function does not permit inference of '%0'">;
+def note_func_effect_call_virtual : Note<
+  "virtual method cannot be inferred '%0'">;
+def note_func_effect_call_func_ptr : Note<
+  "function pointer cannot be inferred '%0'">;
+def warn_perf_constraint_implies_noexcept : Warning<
+  "'%0' function should be declared noexcept">,
+  InGroup;
+
+// FIXME: It would be nice if we could provide fuller template expansion notes.

dougsonos wrote:

Since the analysis happens at the end of Sema rather than during template 
expansion, I didn't see a way to use the usual template expansion notes. In 
practice this has been a whole lot better than nothing.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-19 Thread Doug Wyatt via cfe-commits


@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t FKind : 3;

dougsonos wrote:

The size of FunctionEffect changes from 4 (sizeof(unsigned)) to 1, and saves on 
memory footprint in the new analysis code.

https://github.com/llvm/llvm-project/pull/99656
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (PR #99656)

2024-07-19 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos created 
https://github.com/llvm/llvm-project/pull/99656

- In Sema, when encountering Decls with function effects needing verification, 
add them to a vector, DeclsWithEffectsToVerify.
- Update AST serialization to include DeclsWithEffectsToVerify.
- In AnalysisBasedWarnings, use DeclsWithEffectsToVerify as a work queue, 
verifying functions with declared effects, and inferring (when permitted and 
necessary) whether their callees have effects.

>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH] nonblocking/nonallocating attributes: 2nd pass caller/callee
 analysis/verification

---
 clang/include/clang/AST/Type.h|2 +-
 clang/include/clang/Basic/DiagnosticGroups.td |1 +
 .../clang/Basic/DiagnosticSemaKinds.td|   49 +
 clang/include/clang/Sema/Sema.h   |   13 +
 .../include/clang/Serialization/ASTBitCodes.h |4 +
 clang/include/clang/Serialization/ASTReader.h |3 +
 clang/include/clang/Serialization/ASTWriter.h |1 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp  | 1259 +
 clang/lib/Sema/SemaDecl.cpp   |   56 +
 clang/lib/Sema/SemaExpr.cpp   |4 +
 clang/lib/Sema/SemaLambda.cpp |5 +
 clang/lib/Serialization/ASTReader.cpp |   19 +
 clang/lib/Serialization/ASTWriter.cpp |   12 +
 .../Sema/attr-nonblocking-constraints.cpp |  194 +++
 clang/test/Sema/attr-nonblocking-syntax.cpp   |1 +
 .../attr-nonblocking-constraints.mm   |   23 +
 16 files changed, 1645 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
 create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..08141f75de8db 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t 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
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e043349..55d9442a939da 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", 
[UnsafeBufferUsageInCon
 // Warnings and notes related to the function effects system underlying
 // the nonblocking and nonallocating attributes.
 def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : 
DiagGroup<"perf-constraint-implies-noexcept">;
 
 // 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 d60f32674ca3a..ec02c02d158c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup>;
 
 // Function effects
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,
+  InGroup;
+def note_func_effect_allocates : Note<
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function must not throw or catch exceptions">,
+  InGroup;
+def note_func_effect_throws_or_catches : Note<
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function must not have static locals">,
+  InGroup;
+def note_func_effect_has_static_local : Note<
+  "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function must not use thread-local variables">,
+  InGroup;
+def note_func_effect_uses_thread_local : Note<
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function must not access an ObjC method or property">,
+  InGroup;
+def note_func_effect_calls_objc : Note<
+  "function cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "'%0' function must not call non-'%0' function '%1'">,
+  InGroup;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "'%0' function must not call non-'%0' expression">,
+  InGroup;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' 

[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-07-17 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

It would be great if we could merge this soon, please; I'd to start another PR 
for the 2nd-pass effects analysis and it has a dependency or two on these 
changes.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-07-12 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

Added a further tweak: add a bool to ASTContext to track whether any 
FunctionProtoType's with effects have been created, so that Sema can 
short-circuit checks involving effects when it knows no one has any. This seems 
to have the desired effect of only paying for the feature when it's in use.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-07-12 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos updated 
https://github.com/llvm/llvm-project/pull/96844

>From 038b39d3235c6c8151127c34d34f498dd298273c Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 26 Jun 2024 10:03:25 -0700
Subject: [PATCH 1/4] Performance optimizations for function effects: - Put new
 FunctionProtoType trailing objects last. - Inline FunctionEffectsRef::get() -
 Manually inline FunctionEffectsRef::Profile().

---
 clang/include/clang/AST/Type.h | 30 +++
 clang/lib/AST/ASTContext.cpp   | 11 +-
 clang/lib/AST/Type.cpp | 37 +++---
 clang/lib/Sema/SemaType.cpp|  1 +
 4 files changed, 41 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 62836ec5c6312..4545ad94d522a 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -132,7 +132,6 @@ class TemplateArgument;
 class TemplateArgumentListInfo;
 class TemplateArgumentLoc;
 class TemplateTypeParmDecl;
-template  class TreeTransform;
 class TypedefNameDecl;
 class UnresolvedUsingTypenameDecl;
 class UsingShadowDecl;
@@ -4899,7 +4898,6 @@ class FunctionEffectsRef {
 return !(LHS == RHS);
   }
 
-  void Profile(llvm::FoldingSetNodeID ) const;
   void dump(llvm::raw_ostream ) const;
 };
 
@@ -4970,7 +4968,7 @@ class FunctionProtoType final
   FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
   Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers> {
+  Qualifiers, FunctionEffect, EffectConditionExpr> {
   friend class ASTContext; // ASTContext creates these.
   friend TrailingObjects;
 
@@ -5001,21 +4999,21 @@ class FunctionProtoType final
   //   an ExtParameterInfo for each of the parameters. Present if and
   //   only if hasExtParameterInfos() is true.
   //
+  // * Optionally a Qualifiers object to represent extra qualifiers that can't
+  //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
+  //   only if hasExtQualifiers() 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.
-  //
   // The optional FunctionTypeExtraBitfields has to be before the data
   // related to the exception specification since it contains the number
   // of exception types.
   //
-  // We put the ExtParameterInfos last.  If all were equal, it would make
+  // We put the ExtParameterInfos later.  If all were equal, it would make
   // more sense to put these before the exception specification, because
   // it's much easier to skip past them compared to the elaborate switch
   // required to skip the exception specification.  However, all is not
@@ -5132,6 +5130,10 @@ class FunctionProtoType final
 return hasExtParameterInfos() ? getNumParams() : 0;
   }
 
+  unsigned numTrailingObjects(OverloadToken) const {
+return hasExtQualifiers() ? 1 : 0;
+  }
+
   unsigned numTrailingObjects(OverloadToken) const {
 return getNumFunctionEffects();
   }
@@ -8619,6 +8621,18 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl , llvm::APSInt Val,
  unsigned Scale);
 
+inline FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
+  while (true) {
+QualType Pointee = QT->getPointeeType();
+if (Pointee.isNull())
+  break;
+QT = Pointee;
+  }
+  if (const auto *FPT = QT->getAs())
+return FPT->getFunctionEffects();
+  return {};
+}
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 1b5d16bd176f3..b5519b3fc25a3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4601,14 +4601,15 @@ QualType ASTContext::getFunctionTypeInternal(
   size_t Size = FunctionProtoType::totalSizeToAlloc<
   QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers>(
+  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
+  FunctionEffect, EffectConditionExpr>(
   NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
   EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
   ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
-  EPI.ExtParameterInfos ? 

[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-07-12 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos updated 
https://github.com/llvm/llvm-project/pull/96844

>From 038b39d3235c6c8151127c34d34f498dd298273c Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 26 Jun 2024 10:03:25 -0700
Subject: [PATCH 1/2] Performance optimizations for function effects: - Put new
 FunctionProtoType trailing objects last. - Inline FunctionEffectsRef::get() -
 Manually inline FunctionEffectsRef::Profile().

---
 clang/include/clang/AST/Type.h | 30 +++
 clang/lib/AST/ASTContext.cpp   | 11 +-
 clang/lib/AST/Type.cpp | 37 +++---
 clang/lib/Sema/SemaType.cpp|  1 +
 4 files changed, 41 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 62836ec5c6312..4545ad94d522a 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -132,7 +132,6 @@ class TemplateArgument;
 class TemplateArgumentListInfo;
 class TemplateArgumentLoc;
 class TemplateTypeParmDecl;
-template  class TreeTransform;
 class TypedefNameDecl;
 class UnresolvedUsingTypenameDecl;
 class UsingShadowDecl;
@@ -4899,7 +4898,6 @@ class FunctionEffectsRef {
 return !(LHS == RHS);
   }
 
-  void Profile(llvm::FoldingSetNodeID ) const;
   void dump(llvm::raw_ostream ) const;
 };
 
@@ -4970,7 +4968,7 @@ class FunctionProtoType final
   FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
   Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers> {
+  Qualifiers, FunctionEffect, EffectConditionExpr> {
   friend class ASTContext; // ASTContext creates these.
   friend TrailingObjects;
 
@@ -5001,21 +4999,21 @@ class FunctionProtoType final
   //   an ExtParameterInfo for each of the parameters. Present if and
   //   only if hasExtParameterInfos() is true.
   //
+  // * Optionally a Qualifiers object to represent extra qualifiers that can't
+  //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
+  //   only if hasExtQualifiers() 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.
-  //
   // The optional FunctionTypeExtraBitfields has to be before the data
   // related to the exception specification since it contains the number
   // of exception types.
   //
-  // We put the ExtParameterInfos last.  If all were equal, it would make
+  // We put the ExtParameterInfos later.  If all were equal, it would make
   // more sense to put these before the exception specification, because
   // it's much easier to skip past them compared to the elaborate switch
   // required to skip the exception specification.  However, all is not
@@ -5132,6 +5130,10 @@ class FunctionProtoType final
 return hasExtParameterInfos() ? getNumParams() : 0;
   }
 
+  unsigned numTrailingObjects(OverloadToken) const {
+return hasExtQualifiers() ? 1 : 0;
+  }
+
   unsigned numTrailingObjects(OverloadToken) const {
 return getNumFunctionEffects();
   }
@@ -8619,6 +8621,18 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl , llvm::APSInt Val,
  unsigned Scale);
 
+inline FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
+  while (true) {
+QualType Pointee = QT->getPointeeType();
+if (Pointee.isNull())
+  break;
+QT = Pointee;
+  }
+  if (const auto *FPT = QT->getAs())
+return FPT->getFunctionEffects();
+  return {};
+}
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 1b5d16bd176f3..b5519b3fc25a3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4601,14 +4601,15 @@ QualType ASTContext::getFunctionTypeInternal(
   size_t Size = FunctionProtoType::totalSizeToAlloc<
   QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers>(
+  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
+  FunctionEffect, EffectConditionExpr>(
   NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
   EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
   ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
-  EPI.ExtParameterInfos ? 

[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> I guess you meant to link to 
> https://llvm-compile-time-tracker.com/compare.php?from=f03cb005eb4ba3c6fb645aca2228e907db8cd452=038b39d3235c6c8151127c34d34f498dd298273c=instructions:u
>  directly?

Thanks!

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -5132,6 +5130,10 @@ class FunctionProtoType final
 return hasExtParameterInfos() ? getNumParams() : 0;
   }
 
+  unsigned numTrailingObjects(OverloadToken) const {

dougsonos wrote:

Seems necessary when making Qualifiers no longer last.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -8619,6 +8621,18 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl , llvm::APSInt Val,
  unsigned Scale);
 
+inline FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {

dougsonos wrote:

Same implementation as before, just inlined since it's heavily used.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -7623,6 +7623,7 @@ 
handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState ,
   FunctionEffectSet FX(EPI.FunctionEffects);
   FunctionEffectSet::Conflicts Errs;
   bool Success = FX.insert(NewEC, Errs);
+  (void)Success;

dougsonos wrote:

>From the previous branch - was generating a warning when assertions are 
>disabled.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -3857,9 +3857,19 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID 
, QualType Result,
   }
 
   epi.ExtInfo.Profile(ID);
-  ID.AddInteger((epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
 
-  epi.FunctionEffects.Profile(ID);
+  unsigned EffectCount = epi.FunctionEffects.size();
+  bool HasConds = !epi.FunctionEffects.Conditions.empty();
+
+  ID.AddInteger(
+  (EffectCount << 3) | (HasConds << 2) |
+  (epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
+
+  for (unsigned Idx = 0; Idx != EffectCount; ++Idx) {
+   ID.AddInteger(epi.FunctionEffects.Effects[Idx].toOpaqueInt32());

dougsonos wrote:

Oops, I forgot to run clang-format.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -3857,9 +3857,19 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID 
, QualType Result,
   }
 
   epi.ExtInfo.Profile(ID);
-  ID.AddInteger((epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
 
-  epi.FunctionEffects.Profile(ID);
+  unsigned EffectCount = epi.FunctionEffects.size();
+  bool HasConds = !epi.FunctionEffects.Conditions.empty();
+
+  ID.AddInteger(

dougsonos wrote:

It was slightly measurably better to use more bits of this existing integer.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits


@@ -132,7 +132,6 @@ class TemplateArgument;
 class TemplateArgumentListInfo;
 class TemplateArgumentLoc;
 class TemplateTypeParmDecl;
-template  class TreeTransform;

dougsonos wrote:

Was made unnecessary by a late change in the previous branch.

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

This reduces the compile-time regression introduced by 
f03cb005eb4ba3c6fb645aca2228e907db8cd452
[Clang] Introduce `nonblocking`/`nonallocating` attributes (#84983)

https://github.com/llvm/llvm-project/pull/96844
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] Performance optimizations for function effects (nonblocking attribute etc.) (PR #96844)

2024-06-26 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos created 
https://github.com/llvm/llvm-project/pull/96844

- Put new FunctionProtoType trailing objects last.
- Inline FunctionEffectsRef::get()
- Manually inline FunctionEffectsRef::Profile().

>From 038b39d3235c6c8151127c34d34f498dd298273c Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 26 Jun 2024 10:03:25 -0700
Subject: [PATCH] Performance optimizations for function effects: - Put new
 FunctionProtoType trailing objects last. - Inline FunctionEffectsRef::get() -
 Manually inline FunctionEffectsRef::Profile().

---
 clang/include/clang/AST/Type.h | 30 +++
 clang/lib/AST/ASTContext.cpp   | 11 +-
 clang/lib/AST/Type.cpp | 37 +++---
 clang/lib/Sema/SemaType.cpp|  1 +
 4 files changed, 41 insertions(+), 38 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 62836ec5c6312..4545ad94d522a 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -132,7 +132,6 @@ class TemplateArgument;
 class TemplateArgumentListInfo;
 class TemplateArgumentLoc;
 class TemplateTypeParmDecl;
-template  class TreeTransform;
 class TypedefNameDecl;
 class UnresolvedUsingTypenameDecl;
 class UsingShadowDecl;
@@ -4899,7 +4898,6 @@ class FunctionEffectsRef {
 return !(LHS == RHS);
   }
 
-  void Profile(llvm::FoldingSetNodeID ) const;
   void dump(llvm::raw_ostream ) const;
 };
 
@@ -4970,7 +4968,7 @@ class FunctionProtoType final
   FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
   Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers> {
+  Qualifiers, FunctionEffect, EffectConditionExpr> {
   friend class ASTContext; // ASTContext creates these.
   friend TrailingObjects;
 
@@ -5001,21 +4999,21 @@ class FunctionProtoType final
   //   an ExtParameterInfo for each of the parameters. Present if and
   //   only if hasExtParameterInfos() is true.
   //
+  // * Optionally a Qualifiers object to represent extra qualifiers that can't
+  //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
+  //   only if hasExtQualifiers() 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.
-  //
   // The optional FunctionTypeExtraBitfields has to be before the data
   // related to the exception specification since it contains the number
   // of exception types.
   //
-  // We put the ExtParameterInfos last.  If all were equal, it would make
+  // We put the ExtParameterInfos later.  If all were equal, it would make
   // more sense to put these before the exception specification, because
   // it's much easier to skip past them compared to the elaborate switch
   // required to skip the exception specification.  However, all is not
@@ -5132,6 +5130,10 @@ class FunctionProtoType final
 return hasExtParameterInfos() ? getNumParams() : 0;
   }
 
+  unsigned numTrailingObjects(OverloadToken) const {
+return hasExtQualifiers() ? 1 : 0;
+  }
+
   unsigned numTrailingObjects(OverloadToken) const {
 return getNumFunctionEffects();
   }
@@ -8619,6 +8621,18 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl , llvm::APSInt Val,
  unsigned Scale);
 
+inline FunctionEffectsRef FunctionEffectsRef::get(QualType QT) {
+  while (true) {
+QualType Pointee = QT->getPointeeType();
+if (Pointee.isNull())
+  break;
+QT = Pointee;
+  }
+  if (const auto *FPT = QT->getAs())
+return FPT->getFunctionEffects();
+  return {};
+}
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 1b5d16bd176f3..b5519b3fc25a3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4601,14 +4601,15 @@ QualType ASTContext::getFunctionTypeInternal(
   size_t Size = FunctionProtoType::totalSizeToAlloc<
   QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
   FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
-  FunctionEffect, EffectConditionExpr, Qualifiers>(
+  Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
+  FunctionEffect, EffectConditionExpr>(
   NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
   

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

2024-06-26 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> So it sounds like the act of adding an additional trailing object will slow 
> down the performance regardless of whether we use the new trailing object?

I was confused by some comments on `FunctionProtoType` which have diverged from 
the source, and thought that it was necessary to leave Qualifiers as the last 
trailing object. I have a preliminary run which suggests that by moving the new 
trailing objects to the end instead of in front of Qualifiers, that will regain 
some performance.

I also found a hot method to inline. Aiming to open another PR today.


https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-24 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> I’m probably just stating the obvious here, but seeing as existing code 
> obviously does not make use of effects, for compiling it to become slower, 
> that means that we’re probably unconditionally executing code somewhere 
> irrespective of whether effects are present or not, so what I’m saying is we 
> should probably investigate any places where this pr checks for the presence 
> of effects and does something based on that and move the checks for whether a 
> function even has effects attached to it as far up as possible and try and 
> also optimise that check (is the function that checks for that defined in a 
> header? If not, we should maybe move the definition there so it can be 
> inlined).

That makes sense. I remember thinking a couple of times that I was working on a 
function that might be better inlined, but changing Type.h triggered long 
rebuilds.

> The only other option I can think of is that this adds some more data to 
> `FunctionProtoType`s, which means we allocate more etc. but that alone 
> shouldn’t have this big of an impact I’m assuming?

Well, it adds two more types of trailing objects so nothing more gets allocated 
in the case of no effects. Possibly the trailing objects mechanism itself gets 
a bit slower though.

I'll dig in and investigate today.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-20 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-20 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-20 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-20 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-20 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-18 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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]];

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-18 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-18 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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]]; // 

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

2024-06-18 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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}}

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-18 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-06-18 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,141 @@
+// 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 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 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
+// 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.

dougsonos wrote:

Conceptually, we have to err on the side of conservative correctness here:
```
struct Base { virtual void process() [[clang::nonblocking]]; };
struct Derived { void process() override; } // inherits the attribute
// (we did discuss making this a 

[clang] [compiler-rt] [compiler-rt] Realtime Sanitizer: Introduce RADSan backend (PR #92460)

2024-05-30 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> Looking for weighing in on any of the code as it stands now, or the naming 
> question posed by MaskRay 
> [here](https://discourse.llvm.org/t/rfc-nolock-and-noalloc-attributes/76837/75),
>  so far there seems to be support for rtsan over radsan :)

+1 for rtsan

https://github.com/llvm/llvm-project/pull/92460
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-27 Thread Doug Wyatt via cfe-commits


@@ -7525,25 +7525,21 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
 
 std::optional
 Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
-  auto BadExpr = [&]() {
-Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
-<< ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
-<< CondExpr->getSourceRange();
+  if (DiagnoseUnexpandedParameterPack(CondExpr))
 return std::nullopt;
-  };
-
-  if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
-if (CondExpr->containsUnexpandedParameterPack())
-  return BadExpr();
+  if (CondExpr->isTypeDependent() || CondExpr->isValueDependent())
 return FunctionEffectMode::Dependent;
-  }
 
   std::optional ConditionValue =
   CondExpr->getIntegerConstantExpr(Context);
-  if (!ConditionValue)
-return BadExpr();
-  return ConditionValue->getExtValue() ? FunctionEffectMode::True
-   : FunctionEffectMode::False;
+  if (!ConditionValue) {
+Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
+<< ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant

dougsonos wrote:

I did that, with a FIXME about the inconsistency of that diagnostic.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-27 Thread Doug Wyatt via cfe-commits


@@ -7525,25 +7525,21 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
 
 std::optional
 Sema::ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName) {
-  auto BadExpr = [&]() {
-Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
-<< ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant
-<< CondExpr->getSourceRange();
+  if (DiagnoseUnexpandedParameterPack(CondExpr))
 return std::nullopt;
-  };
-
-  if (CondExpr->isTypeDependent() || CondExpr->isValueDependent()) {
-if (CondExpr->containsUnexpandedParameterPack())
-  return BadExpr();
+  if (CondExpr->isTypeDependent() || CondExpr->isValueDependent())
 return FunctionEffectMode::Dependent;
-  }
 
   std::optional ConditionValue =
   CondExpr->getIntegerConstantExpr(Context);
-  if (!ConditionValue)
-return BadExpr();
-  return ConditionValue->getExtValue() ? FunctionEffectMode::True
-   : FunctionEffectMode::False;
+  if (!ConditionValue) {
+Diag(CondExpr->getExprLoc(), diag::err_attribute_argument_type)
+<< ("'" + AttributeName.str() + "'") << AANT_ArgumentIntegerConstant

dougsonos wrote:

The difficulty here is:
- this is a pre-existing diagnostic; I didn't see a need for a new one just for 
quotes
- the other users  of the pre-existing diagnostic use `<<` with a `ParsedAttr`, 
which apparently quotes the attribute (?), but this function can be called from 
`TreeTransform` (for template instantiation), where there is only a 
`FunctionEffect` (whose name is passed in).

And then there are usages like:
```
Diag(AttrLoc, diag::err_attribute_argument_type)
<< "vector_size" << AANT_ArgumentIntegerConstant
<< SizeExpr->getSourceRange();
```
, passing a string constant; it would appear that in this case the attribute 
isn't quoted, which seems inconsistent.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-27 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> @dougsonos Just merged a pr that diagnoses unexpanded parameter packs when an 
> attribute is parsed, so could you double-check if that call to 
> `DiagnoseUnexpandedParameterPack` is still necessary?

I just re-merged main to the branch and verified that the call to 
`DiagnoseUnexpandedParameterPack` is no longer needed.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-24 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-24 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-24 Thread Doug Wyatt via cfe-commits


@@ -4640,6 +4647,306 @@ 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.
+  /// 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 ) 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 CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return !(LHS == RHS);
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+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 {

dougsonos wrote:

How about
`EffectConditionExpr` : the struct holding an `Expr*` for disambiguation with 
trailing objects
`FunctionEffectWithCondition` : combining `FunctionEffect` and 
`FunctionEffectWithCondition`
?

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

I've been through all the feedback again and believe I have addressed 
everything.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -10510,6 +10512,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, 
QualType rhs,
 if (lproto->getMethodQuals() != rproto->getMethodQuals())
   return {};
 
+// TODO: (nonblocking) Does anything need to be done with FunctionEffects?
+

dougsonos wrote:

This now creates a composite type using either the intersection or union of the 
effects, depending on context -- replicating the logic of `noreturn`.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -7973,3 +7973,20 @@ requirement:
   }
   }];
 }
+
+def NoLockNoAllocDocs : Documentation {
+  let Category = DocCatType;
+  let Content = [{
+The ``nolock`` and ``noalloc`` attributes can be attached to functions, 
blocks, 
+function pointers, lambdas, and member functions. The attributes identify code
+which must not allocate memory or lock, and the compiler uses the attributes to
+verify these requirements.
+
+Like ``noexcept``, ``nolock`` and ``noalloc`` have an optional argument, a
+compile-time constant boolean expression. By default, the argument is true, so
+``[[clang::nolock(true)]]`` is equivalent to ``[[clang::nolock]]``, and 
declares
+the function type as never locking.
+
+TODO: how much of the RFC to include here? Make it a separate page?

dougsonos wrote:

done :)

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -4639,6 +4645,303 @@ 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.
+  /// 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 ) 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 CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return !(LHS == RHS);
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+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 ) 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;
+
+  FunctionEffectWithCondition() = default;
+  FunctionEffectWithCondition(const FunctionEffect ,
+  const FunctionEffectCondition )
+  : Effect(E), Cond(C) {}
+
+  /// Return a textual description of the effect, and its condition, if any.
+  std::string description() const;
+
+  friend bool operator<(const FunctionEffectWithCondition ,
+const FunctionEffectWithCondition ) {
+if (LHS.Effect < RHS.Effect)
+   

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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -4429,6 +4434,284 @@ class FunctionNoProtoType : public FunctionType, public 
llvm::FoldingSetNode {
   }
 };
 
+// 
--
+
+class Decl;
+class CXXMethodDecl;
+class FunctionTypeEffectsRef;
+class FunctionTypeEffectSet;
+
+/*
+  TODO: Idea about how to move most of the FunctionEffect business out of
+  Type.h, thus removing these forward declarations.
+
+  - Keep FunctionEffect itself here but make it more minimal. Don't define 
flags
+  or any behaviors, just the Kind and an accessor.
+  - Keep FunctionEffectCondExpr here.
+  - Make FunctionProtoType and ExtProtoInfo use only ArrayRef
+  and ArrayRef.
+  - Somewhere in Sema, define ExtFunctionEffect, which holds a FunctionEffect
+and has all the behavior-related methods.
+  - There too, define the containers. FunctionTypeEffectsRef can have a
+  constructor or factory method that initializes itself from a
+  FunctionProtoType.
+*/

dougsonos wrote:

All cleaned up now

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -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(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 ,
+QualType NewType,
+const FunctionEffectSet ) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl 
,
+   const FunctionEffectSet ,
+   const FunctionDecl ,
+   const FunctionEffectSet ) 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 ,
+   const FunctionEffectSet ,
+   const CXXMethodDecl ,
+   const FunctionEffectSet ) 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 ) 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 ) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind != RHS.FKind;
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+return LHS.FKind < 

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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -17154,6 +17156,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation 
CaretLoc,
   BlockScopeInfo *BSI = cast(FunctionScopes.back());
   BlockDecl *BD = BSI->TheDecl;
 
+  if (const auto FX = BD->getFunctionEffects()) {
+CheckAddCallableWithEffects(BD, FX);
+  }

dougsonos wrote:

follow-on

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -1888,6 +1888,11 @@ ExprResult Sema::BuildCaptureInit(const Capture ,
 ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
   LambdaScopeInfo LSI = *cast(FunctionScopes.back());
   ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+  if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
+CheckAddCallableWithEffects(LSI.CallOperator, FX);
+  }

dougsonos wrote:

will have to revisit this in the follow-on PR

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -4429,6 +4433,218 @@ class FunctionNoProtoType : public FunctionType, public 
llvm::FoldingSetNode {
   }
 };
 
+// 
--
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
+class FunctionEffectSet;
+class TypeSourceInfo;
+
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+  /// Identifies the particular type of effect.
+  enum class Type {
+None = 0,
+NonBlocking,
+NonAllocating,
+  };
+
+  /// Flags describing behaviors of the effect.
+  // (Why not a struct with bitfields? There's one function that would like to
+  // test a caller-specified bit. There are some potential optimizations that
+  // would OR together the bits of multiple effects.)
+  using Flags = unsigned;
+  enum FlagBit : unsigned {
+// 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,
+Propagate // Base method's effects are merged with those of the override.
+  };
+
+private:
+  // For uniqueness, currently only Type_ is significant.
+
+  LLVM_PREFERRED_TYPE(Type)
+  unsigned Type_ : 2;
+  Flags Flags_ : 8; // A constant function of Type but cached here.
+
+  // Expansion: for hypothetical TCB+types, there could be one Type for TCB,
+  // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
+  // be considered for uniqueness.
+
+  // Since this struct is serialized as if it were a uint32_t, it's important
+  // to pad and explicitly zero the extra bits.
+  [[maybe_unused]] unsigned Padding : 22;
+
+public:
+  using CalleeDeclOrType =
+  llvm::PointerUnion;
+
+  FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0), Padding(0) {}
+
+  explicit FunctionEffect(Type T);
+
+  /// The type of the effect.
+  Type type() const { return Type(Type_); }
+
+  /// Flags describing behaviors of the effect.
+  Flags flags() const { return Flags_; }
+
+  /// The description printed in diagnostics, e.g. 'nonblocking'.
+  StringRef name() const;
+
+  /// A hashable representation.
+  uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
+
+  /// Return true if adding or removing the effect as part of a type conversion
+  /// should generate a diagnostic.
+  bool diagnoseConversion(bool Adding, QualType OldType,
+  FunctionEffectSet OldFX, QualType NewType,
+  FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool diagnoseRedeclaration(bool Adding, const FunctionDecl ,
+ FunctionEffectSet OldFX,
+ const FunctionDecl ,
+ FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method
+  /// override should generate a diagnostic.
+  OverrideResult diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl ,
+FunctionEffectSet OldFX,
+const CXXMethodDecl ,
+FunctionEffectSet NewFX) const;
+
+  /// Return true if the effect is allowed to be inferred on a Decl of the
+  /// specified type (generally a FunctionProtoType but TypeSourceInfo is
+  /// provided so any AttributedType sugar can be examined). TSI can be null
+  /// on an implicit function like a default constructor.
+  ///
+  /// 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(QualType QT, const TypeSourceInfo *FType) 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 diagnoseFunctionCall(bool Direct, FunctionEffectSet CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.Type_ == RHS.Type_;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.Type_ != RHS.Type_;
+  }
+  friend bool operator<(const FunctionEffect , const 

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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -5016,3 +5024,254 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
   getTypeConstraintConcept(), getTypeConstraintArguments());
 }
+
+FunctionEffect::FunctionEffect(Type T)
+: Type_(static_cast(T)), Flags_(0), Padding(0) {
+  switch (T) {
+  case Type::NonBlocking:
+Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeStaticLocalVars |
+ FE_ExcludeThreadLocalVars;
+break;
+
+  case Type::NonAllocating:
+// Same as NonBlocking, except without FE_ExcludeStaticLocalVars
+Flags_ = FE_InferrableOnCallees | FE_ExcludeThrow | FE_ExcludeCatch |
+ FE_ExcludeObjCMessageSend | FE_ExcludeThreadLocalVars;
+break;
+  default:
+break;
+  }
+}
+
+StringRef FunctionEffect::name() const {
+  switch (type()) {
+  default:
+return "";
+  case Type::NonBlocking:
+return "nonblocking";
+  case Type::NonAllocating:
+return "nonallocating";
+  }
+}
+
+bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
+FunctionEffectSet OldFX,
+QualType NewType,
+FunctionEffectSet NewFX) const {
+
+  switch (type()) {
+  case Type::NonAllocating:
+// nonallocating can't be added (spoofed) during a conversion, unless we
+// have nonblocking
+if (Adding) {
+  for (const auto  : OldFX) {
+if (Effect.type() == Type::NonBlocking)
+  return false;
+  }
+}
+[[fallthrough]];
+  case Type::NonBlocking:
+// nonblocking can't be added (spoofed) during a conversion
+return Adding;
+  default:
+break;
+  }
+  return false;
+}
+
+bool FunctionEffect::diagnoseRedeclaration(bool Adding,
+   const FunctionDecl ,
+   FunctionEffectSet OldFX,
+   const FunctionDecl ,
+   FunctionEffectSet NewFX) const {
+  switch (type()) {
+  case Type::NonAllocating:
+  case Type::NonBlocking:
+// nonblocking/nonallocating can't be removed in a redeclaration
+// adding -> false, removing -> true (diagnose)
+return !Adding;
+  default:
+break;
+  }
+  return false;
+}
+
+FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
+bool Adding, const CXXMethodDecl , FunctionEffectSet OldFX,
+const CXXMethodDecl , FunctionEffectSet NewFX) const {
+  switch (type()) {
+  case Type::NonAllocating:
+  case Type::NonBlocking:
+// if added on an override, that's fine and not diagnosed.
+// if missing from an override (removed), propagate from base to derived.
+return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
+  default:
+break;
+  }
+  return OverrideResult::Ignore;
+}
+
+bool FunctionEffect::canInferOnFunction(QualType QT,
+const TypeSourceInfo *FType) const {
+  switch (type()) {
+  case Type::NonAllocating:
+  case Type::NonBlocking: {
+// Does the sugar have nonblocking(false) / nonallocating(false) ?
+if (QT->hasAttr(type() == Type::NonBlocking ? attr::Kind::Blocking
+: attr::Kind::Allocating)) {
+  return false;
+}
+
+return true;
+  }
+
+  default:
+break;
+  }
+  return false;
+}
+
+bool FunctionEffect::diagnoseFunctionCall(bool Direct,
+  FunctionEffectSet CalleeFX) const {
+  switch (type()) {
+  case Type::NonAllocating:
+  case Type::NonBlocking: {
+const Type CallerType = type();
+for (const auto  : CalleeFX) {
+  const Type ET = Effect.type();
+  // Does callee have same or stronger constraint?
+  if (ET == CallerType ||
+  (CallerType == Type::NonAllocating && ET == Type::NonBlocking)) {
+return false; // no diagnostic
+  }
+}
+return true; // warning
+  }
+  default:
+break;
+  }
+  return false;
+}
+
+// =
+
+MutableFunctionEffectSet::MutableFunctionEffectSet(
+const FunctionEffect ) {
+  push_back(effect);
+}
+
+void MutableFunctionEffectSet::insert(const FunctionEffect ) {
+  const auto  = std::lower_bound(begin(), end(), Effect);
+  if (Iter == end() || *Iter != Effect) {
+insert(Iter, Effect);
+  }
+}
+
+MutableFunctionEffectSet &
+MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
+  // TODO: For large RHS sets, use set_union or a custom insert-in-place
+  for (const auto  : RHS) {
+insert(Effect);
+  }
+  return *this;
+}
+
+FunctionEffectSet FunctionEffectSet::getUnion(ASTContext ,
+  const FunctionEffectSet ,
+  const FunctionEffectSet ) {
+  // Optimize for one of the two 

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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -3144,6 +3154,9 @@ class Sema final {
   QualType T, TypeSourceInfo *TSInfo,
   StorageClass SC);
 
+  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+  void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);

dougsonos wrote:

this is now part of the follow-on PR

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -352,6 +352,9 @@ let Class = FunctionProtoType in {
   def : Property<"AArch64SMEAttributes", UInt32> {
 let Read = [{ node->getAArch64SMEAttributes() }];
   }
+  def : Property<"functionEffects", Array> {
+let Read = [{ node->getFunctionEffects().serializable() }];
+  }

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -4429,6 +4433,218 @@ class FunctionNoProtoType : public FunctionType, public 
llvm::FoldingSetNode {
   }
 };
 
+// 
--
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
+class FunctionEffectSet;
+class TypeSourceInfo;
+
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+  /// Identifies the particular type of effect.
+  enum class Type {
+None = 0,
+NonBlocking,
+NonAllocating,
+  };
+
+  /// Flags describing behaviors of the effect.
+  // (Why not a struct with bitfields? There's one function that would like to
+  // test a caller-specified bit. There are some potential optimizations that
+  // would OR together the bits of multiple effects.)
+  using Flags = unsigned;
+  enum FlagBit : unsigned {
+// 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,
+Propagate // Base method's effects are merged with those of the override.
+  };
+
+private:
+  // For uniqueness, currently only Type_ is significant.
+
+  LLVM_PREFERRED_TYPE(Type)
+  unsigned Type_ : 2;
+  Flags Flags_ : 8; // A constant function of Type but cached here.
+
+  // Expansion: for hypothetical TCB+types, there could be one Type for TCB,
+  // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
+  // be considered for uniqueness.
+
+  // Since this struct is serialized as if it were a uint32_t, it's important
+  // to pad and explicitly zero the extra bits.
+  [[maybe_unused]] unsigned Padding : 22;
+
+public:
+  using CalleeDeclOrType =
+  llvm::PointerUnion;
+
+  FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0), Padding(0) {}
+
+  explicit FunctionEffect(Type T);
+
+  /// The type of the effect.
+  Type type() const { return Type(Type_); }
+
+  /// Flags describing behaviors of the effect.
+  Flags flags() const { return Flags_; }
+
+  /// The description printed in diagnostics, e.g. 'nonblocking'.
+  StringRef name() const;
+
+  /// A hashable representation.
+  uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
+
+  /// Return true if adding or removing the effect as part of a type conversion
+  /// should generate a diagnostic.
+  bool diagnoseConversion(bool Adding, QualType OldType,
+  FunctionEffectSet OldFX, QualType NewType,
+  FunctionEffectSet NewFX) const;

dougsonos wrote:

This ended up being a method on FunctionEffectDiff in Sema.h

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -464,6 +466,16 @@ class ASTContext : public RefCountedBase {
   /// This is the top-level (C++20) Named module we are building.
   Module *CurrentCXXNamedModule = nullptr;
 
+  class FunctionEffectSetUniquing {
+llvm::DenseSet> Set;
+
+  public:
+FunctionEffectSet getUniqued(llvm::ArrayRef FX);
+
+~FunctionEffectSetUniquing();
+  };
+  FunctionEffectSetUniquing UniquedFunctionEffectSet;
+

dougsonos wrote:

The uniquing went away

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-22 Thread Doug Wyatt via cfe-commits


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
   getTypeConstraintConcept(), getTypeConstraintArguments());
 }
+
+FunctionEffect::~FunctionEffect() = default;
+
+bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
+FunctionEffectSet OldFX,
+QualType NewType,
+FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseRedeclaration(bool Adding,
+   const FunctionDecl ,
+   FunctionEffectSet OldFX,
+   const FunctionDecl ,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl ,
+FunctionEffectSet OldFX,
+const CXXMethodDecl ,
+FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::canInferOnDecl(const Decl *Caller,
+FunctionEffectSet CallerFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
+  FunctionEffectSet CallerFX,
+  CalleeDeclOrType Callee,
+  FunctionEffectSet CalleeFX) const {
+  return false;
+}
+
+const NoLockNoAllocEffect ::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect ::noalloc_instance() {
+  static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
+  return global;
+}
+
+// TODO: Separate flags for noalloc
+NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType Ty, const char *Name)
+: FunctionEffect(Ty,
+ kRequiresVerification | kVerifyCalls |
+ kInferrableOnCallees | kExcludeThrow | kExcludeCatch |
+ kExcludeObjCMessageSend | kExcludeStaticLocalVars |
+ kExcludeThreadLocalVars,
+ Name) {}
+
+NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
+
+std::string NoLockNoAllocEffect::attribute() const {
+  return std::string{"__attribute__((clang_"} + name().str() + "))";
+}
+
+bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
+ FunctionEffectSet OldFX,
+ QualType NewType,
+ FunctionEffectSet NewFX) const {
+  // noalloc can't be added (spoofed) during a conversion, unless we have 
nolock
+  if (Adding) {
+if (!isNoLock()) {
+  for (const auto *Effect : OldFX) {
+if (Effect->type() == kNoLockTrue)
+  return false;
+  }
+}
+// nolock can't be added (spoofed) during a conversion.
+return true;
+  }
+  return false;
+}
+
+bool NoLockNoAllocEffect::diagnoseRedeclaration(bool Adding,
+const FunctionDecl 
,
+FunctionEffectSet OldFX,
+const FunctionDecl 
,
+FunctionEffectSet NewFX) const 
{
+  // nolock/noalloc can't be removed in a redeclaration
+  // adding -> false, removing -> true (diagnose)
+  return !Adding;
+}
+
+bool NoLockNoAllocEffect::diagnoseMethodOverride(
+bool Adding, const CXXMethodDecl , FunctionEffectSet OldFX,
+const CXXMethodDecl , FunctionEffectSet NewFX) const {
+  // nolock/noalloc can't be removed from an override
+  return !Adding;
+}
+
+bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
+ FunctionEffectSet CallerFX) const {
+  // Does the Decl have nolock(false) / noalloc(false) ?
+  QualType QT;
+  if (isa(Caller)) {
+const auto *TSI = cast(Caller)->getSignatureAsWritten();
+QT = TSI->getType();
+  } else if (isa(Caller)) {
+QT = cast(Caller)->getType();
+  } else {
+return false;
+  }
+  if (QT->hasAttr(isNoLock() ? attr::Kind::NoLock : attr::Kind::NoAlloc)) {
+return false;
+  }
+
+  return true;
+}
+
+// TODO: Notice that we don't care about some of the parameters. Is the
+// interface overly general?
+bool NoLockNoAllocEffect::diagnoseFunctionCall(
+bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
+CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
+  const EffectType CallerType = type();
+  for (const auto *Effect : CalleeFX) {
+  

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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -7963,6 +7967,148 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode 
,
+   bool RequireConstexpr) {
+  // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+  if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ExprResult E = PerformContextuallyConvertToBool(CondExpr);
+if (E.isInvalid())
+  return E;
+CondExpr = E.get();
+if (RequireConstexpr || !CondExpr->isValueDependent()) {
+  llvm::APSInt CondInt;
+  E = VerifyIntegerConstantExpression(
+  E.get(), ,
+  // TODO: have our own diagnostic
+  diag::err_constexpr_if_condition_expression_is_not_constant);
+  if (E.isInvalid()) {
+return E;
+  }
+  Mode =
+  (CondInt != 0) ? FunctionEffectMode::True : 
FunctionEffectMode::False;
+} else {
+  Mode = FunctionEffectMode::Dependent;
+}
+  } else {
+Mode = FunctionEffectMode::Dependent;
+  }
+  return CondExpr;
+}
+
+static bool
+handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState ,
+   ParsedAttr , QualType ,
+   FunctionTypeUnwrapper ) {
+  // Delay if this is not a function type.
+  if (!Unwrapped.isFunctionType())
+return false;
+
+  // Require FunctionProtoType
+  auto *FPT = Unwrapped.get()->getAs();
+  if (FPT == nullptr) {
+// TODO: special diagnostic?

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -7963,6 +7967,148 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode 
,
+   bool RequireConstexpr) {
+  // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+  if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ExprResult E = PerformContextuallyConvertToBool(CondExpr);
+if (E.isInvalid())
+  return E;
+CondExpr = E.get();
+if (RequireConstexpr || !CondExpr->isValueDependent()) {
+  llvm::APSInt CondInt;
+  E = VerifyIntegerConstantExpression(
+  E.get(), ,
+  // TODO: have our own diagnostic
+  diag::err_constexpr_if_condition_expression_is_not_constant);

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-21 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos edited 
https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -7963,6 +7967,148 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode 
,
+   bool RequireConstexpr) {
+  // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+  if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ExprResult E = PerformContextuallyConvertToBool(CondExpr);
+if (E.isInvalid())
+  return E;
+CondExpr = E.get();
+if (RequireConstexpr || !CondExpr->isValueDependent()) {
+  llvm::APSInt CondInt;
+  E = VerifyIntegerConstantExpression(

dougsonos wrote:

`Sema::ActOnEffectExpression()` is now much simpler -- it checks for dependence 
and unexpanded parameter packs before trying to evaluate the expression as an 
integer constant.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -7963,6 +7967,148 @@ static Attr *getCCTypeAttr(ASTContext , ParsedAttr 
) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+ExprResult Sema::ActOnEffectExpression(Expr *CondExpr, FunctionEffectMode 
,
+   bool RequireConstexpr) {
+  // see checkFunctionConditionAttr, Sema::CheckCXXBooleanCondition
+  if (RequireConstexpr || !CondExpr->isTypeDependent()) {
+ExprResult E = PerformContextuallyConvertToBool(CondExpr);

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -5028,3 +5050,376 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffectDiff::shouldDiagnoseConversion(
+QualType SrcType, const FunctionEffectsRef , QualType DstType,
+const FunctionEffectsRef ) 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  : 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:
+  return true; // TODO: ???
+}
+  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 , const FunctionEffectsRef ,
+const FunctionDecl , const FunctionEffectsRef ) 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 , const FunctionEffectsRef ,
+const CXXMethodDecl , const FunctionEffectsRef ) 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");
+}
+
+bool FunctionEffect::canInferOnFunction(const Decl ) 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())
+  CalleeFX = BD->getFunctionEffects();
+else
+  return false;
+for (const FunctionEffectWithCondition  : 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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::shouldDiagnoseFunctionCall(
+bool Direct, ArrayRef CalleeFX) const {
+  switch (kind()) {
+  case Kind::NonAllocating:
+  case 

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

2024-05-21 Thread Doug Wyatt via cfe-commits


@@ -5028,3 +5050,376 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffectDiff::shouldDiagnoseConversion(
+QualType SrcType, const FunctionEffectsRef , QualType DstType,
+const FunctionEffectsRef ) 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  : 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:
+  return true; // TODO: ???

dougsonos wrote:

There are better comments around all of these now.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-18 Thread Doug Wyatt via cfe-commits


@@ -1870,6 +1870,28 @@ bool Sema::IsFunctionConversion(QualType FromType, 
QualType ToType,
   FromFn = QT->getAs();
   Changed = true;
 }
+
+// For C, when called from checkPointerTypesForAssignment,
+// we need not to alter FromFn, or else even an innocuous cast
+// like dropping effects will fail. In C++ however we do want to
+// alter FromFn. TODO: Is this correct?

dougsonos wrote:

The test case which exposes a need for this kind of logic is:
```
void nonblocking() [[clang::nonblocking]];
void type_conversions() {
  void (*fp_plain)();
  fp_plain = nonblocking;
}
```

The call chain to `IsFunctionConversion` is:
```
CheckSingleAssignmentConversion
PerformImplicitConversion (in C++ only, not plain C)
TryImplicitConversion
IsStandardConversion
IsFunctionConversion
```

`IsFunctionConversion` drops `noreturn` and `noexcept` from the function type 
very similarly to what this code does for effects. If this code is disabled, 
then an implicit conversion between the two canonically different types fails. 
Then `TryUserDefinedConversion` fails too.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-17 Thread Doug Wyatt via cfe-commits


@@ -4639,6 +4645,303 @@ 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.
+  /// 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 ) 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 CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return !(LHS == RHS);
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+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 ) 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;
+
+  FunctionEffectWithCondition() = default;
+  FunctionEffectWithCondition(const FunctionEffect ,
+  const FunctionEffectCondition )
+  : Effect(E), Cond(C) {}
+
+  /// Return a textual description of the effect, and its condition, if any.
+  std::string description() const;
+
+  friend bool operator<(const FunctionEffectWithCondition ,
+const FunctionEffectWithCondition ) {
+if (LHS.Effect < RHS.Effect)
+   

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

2024-05-17 Thread Doug Wyatt via cfe-commits


@@ -4639,6 +4645,303 @@ 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.
+  /// 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 ) 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 CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return !(LHS == RHS);
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+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 ) 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;
+
+  FunctionEffectWithCondition() = default;
+  FunctionEffectWithCondition(const FunctionEffect ,
+  const FunctionEffectCondition )
+  : Effect(E), Cond(C) {}
+
+  /// Return a textual description of the effect, and its condition, if any.
+  std::string description() const;
+
+  friend bool operator<(const FunctionEffectWithCondition ,
+const FunctionEffectWithCondition ) {
+if (LHS.Effect < RHS.Effect)
+   

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

2024-05-16 Thread Doug Wyatt via cfe-commits


@@ -5028,3 +5060,263 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::canInferOnFunction(const Decl ) 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())
+  CalleeFX = BD->getFunctionEffects();
+else
+  return false;
+for (const FunctionEffectWithCondition  : 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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::shouldDiagnoseFunctionCall(
+bool Direct, ArrayRef CalleeFX) const {
+  switch (kind()) {
+  case Kind::NonAllocating:
+  case Kind::NonBlocking: {
+const Kind CallerKind = kind();
+for (const auto  : 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:
+break;

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-16 Thread Doug Wyatt via cfe-commits


@@ -5028,3 +5060,263 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   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:
+break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool FunctionEffect::canInferOnFunction(const Decl ) 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())
+  CalleeFX = BD->getFunctionEffects();
+else
+  return false;
+for (const FunctionEffectWithCondition  : 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:
+break;

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-16 Thread Doug Wyatt via cfe-commits


@@ -5028,3 +5060,263 @@ void AutoType::Profile(llvm::FoldingSetNodeID , 
const ASTContext ) {
   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:
+break;

dougsonos wrote:

done

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-16 Thread Doug Wyatt via cfe-commits


@@ -1435,6 +1435,38 @@ def CXX11NoReturn : InheritableAttr {
   let Documentation = [CXX11NoReturnDocs];
 }
 
+def NonBlocking : TypeAttr {
+  let Spellings = [CXX11<"clang", "nonblocking">,

dougsonos wrote:

I've gotten rid of the "clang_" prefix on the GNU-style attributes.

https://github.com/llvm/llvm-project/pull/84983
___
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


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

2024-05-16 Thread Doug Wyatt via cfe-commits


@@ -4639,6 +4645,303 @@ 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.
+  /// 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 ) 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 CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect , const FunctionEffect ) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect , const FunctionEffect ) 
{
+return !(LHS == RHS);
+  }
+  friend bool operator<(const FunctionEffect , const FunctionEffect ) {
+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 ) 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;
+
+  FunctionEffectWithCondition() = default;
+  FunctionEffectWithCondition(const FunctionEffect ,
+  const FunctionEffectCondition )
+  : Effect(E), Cond(C) {}
+
+  /// Return a textual description of the effect, and its condition, if any.
+  std::string description() const;
+
+  friend bool operator<(const FunctionEffectWithCondition ,
+const FunctionEffectWithCondition ) {
+if (LHS.Effect < RHS.Effect)
+   

  1   2   3   >