[clang] nolock/noalloc attributes (PR #84983)

2024-03-12 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] nolock/noalloc attributes (PR #84983)

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

https://github.com/dougsonos converted_to_draft 
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] nolock/noalloc attributes (PR #84983)

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

https://github.com/dougsonos closed 
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] nolock/noalloc attributes (PR #84983)

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

dougsonos wrote:

> > * Does the FunctionEffect / FunctionEffectSet abstraction make sense 
> > (Type.h)? The idea is that an abstract effect system for functions could 
> > easily support things like "TCB with types" or adding "nowait" to the 
> > "nolock/noalloc" group of effects.
> 
> Hmm, this is a difficult question. Generally, I would probably prefer to just 
> hard-code whatever effects we need right now, perhaps as something like a 
> bitmask; from what I recall, none of the effects take arguments or anything 
> like that, so that would be possible in that case (these are just my personal 
> observations, though; I don’t have every last detail of this feature 
> memorised, so if I’m missing something obvious, please let me know); my main 
> concern is that the current implementation might be a bit too general. In 
> particular, this seems like a lot of—if not too much—machinery for 
> implementing a grand total of two function attributes.
> 
> Because, sure, making it extensible doesn’t sound like a bad idea on paper, 
> but adding any effects would likely require significant modifications to 
> other parts of the compiler as well, so if a situation should arise where we 
> do need to handle more complex effects, we should be able to refactor it 
> whenever that comes up. For the time being, a simpler effect system may serve 
> us better (and should also be more straight-forward to refactor if need be).
> 
> Perhaps, let’s say that, unless it’s likely that we may end up adding effects 
> that require more storage than a single bit or two in the foreseeable future, 
> then I’d rather just have it be a bitmask. The way this is implemented 
> currently does remind me at least in part of attributes, but attributes are 
> very varied and _do_ take arguments, so I’m not sure the two systems are 
> really comparable.
> 
> @AaronBallman I recall you having some opinions on adding data to 
> `FunctionProtoType`s the other day, wdyt about this?



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] nolock/noalloc attributes (PR #84983)

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

https://github.com/dougsonos reopened 
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] nolock/noalloc attributes (PR #84983)

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


@@ -1402,6 +1402,29 @@ def CXX11NoReturn : InheritableAttr {
   let Documentation = [CXX11NoReturnDocs];
 }
 
+def NoLock : DeclOrTypeAttr {
+  let Spellings = [CXX11<"clang", "nolock">,
+   C23<"clang", "nolock">,
+   GNU<"clang_nolock">];
+
+  // Subjects - not needed?
+  //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;

dougsonos wrote:

I was cribbing from AnnotateType which also has no Subjects. I added these 
tests, which seem to indicate that type attributes have some built-in defense.
```
int nl_var [[clang::nolock]]; // expected-warning {{ 'nolock' only applies to 
function types; type here is 'int' }}
struct nl_struct {} [[clang::nolock]]; // expected-warning {{ attribute 
'nolock' is ignored, place it after "struct" to apply attribute to type 
declaration }}
struct [[clang::nolock]] nl_struct2 {}; // expected-error {{ 'nolock' attribute 
cannot be applied to a declaration }}
```

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] nolock/noalloc attributes (PR #84983)

2024-03-12 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] nolock/noalloc attributes (PR #84983)

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


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;

dougsonos wrote:

Yeah, the code generating warnings/notes always puts the effect before the 
function name, so there's consistency there and inconsistency here:
```
def warn_func_effect_allocates : Warning<
  "'%0' function '%1' must not allocate or deallocate memory">,
  InGroup;

def note_func_effect_allocates : Note<
  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
```

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] nolock/noalloc attributes (PR #84983)

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


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function '%1' must not throw or catch exceptions">,
+  InGroup;
+
+def note_func_effect_throws_or_catches : Note<
+  "'%1' cannot be inferred '%0' because it throws or catches exceptions">;
+
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function '%1' must not have static locals">,
+  InGroup;
+
+def note_func_effect_has_static_local : Note<
+  "'%1' cannot be inferred '%0' because it has a static local">;
+
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function '%1' must not use thread-local variables">,
+  InGroup;
+
+def note_func_effect_uses_thread_local : Note<
+  "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function '%1' must not access an ObjC method or property">,
+  InGroup;
+
+def note_func_effect_calls_objc : Note<
+  "'%1' cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+
+def warn_func_effect_calls_disallowed_func : Warning<
+  "'%0' function '%1' must not call non-'%0' function '%2'">,
+  InGroup;
+
+// UNTESTED
+def warn_func_effect_calls_disallowed_expr : Warning<
+  "'%0' function '%1' must not call non-'%0' expression">,
+  InGroup;
+
+def note_func_effect_calls_disallowed_func : Note<
+  "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+
+def note_func_effect_call_extern : Note<
+  "'%1' cannot be inferred '%0' because it has no definition in this 
translation unit">;
+
+def note_func_effect_call_not_inferrable : Note<
+  "'%1' does not permit inference of '%0'">;
+
+def note_func_effect_call_virtual : Note<
+  "'%1' cannot be inferred '%0' because it is virtual">;
+
+def note_func_effect_call_func_ptr : Note<
+  "'%1' cannot be inferred '%0' because it is a function pointer">;
+
+// TODO: Not currently being generated
+// def warn_perf_annotation_implies_noexcept : Warning<
+//  "'%0' function should be declared noexcept">,
+//  InGroup;
+
+// TODO: Not currently being generated
+def warn_func_effect_false_on_type : Warning<
+  "only functions/methods/blocks may be declared %0(false)">,
+  InGroup;
+
+// TODO: can the usual template expansion notes be used?
+def note_func_effect_from_template : Note<
+  "in template expansion here">;

dougsonos wrote:

I do want to use those existing notes, but I couldn't figure out how to get the 
instantiation location (from the late stage of Sema where the diagnostics are 
being generated).

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] nolock/noalloc attributes (PR #84983)

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


@@ -3666,11 +3673,14 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID 
&ID, QualType Result,
   // Finally we have a trailing return type flag (bool)
   // combined with AArch64 SME Attributes, to save space:
   //  int
+  // Then add the FunctionEffects
   //
   // There is no ambiguity between the consumed arguments and an empty EH
   // spec because of the leading 'bool' which unambiguously indicates
   // whether the following bool is the EH spec or part of the arguments.
 
+  ID.AddPointer(epi.FunctionEffects.getOpaqueValue()); // TODO: Where???

dougsonos wrote:

Thanks; this call seems to be taking a lot of care about this but it does seem 
to relate to how various parts are optional.

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] nolock/noalloc attributes (PR #84983)

2024-03-12 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] nolock/noalloc attributes (PR #84983)

2024-03-12 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] nolock/noalloc attributes (PR #84983)

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


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}

dougsonos wrote:

I would like it to be this simple, though of the current behaviors of 
noalloc/nolock, the one that seems extra-special is to detect that a function 
is declared `nolock(false)` or `noalloc(false)` (by examining the function's 
type sugar) and short-circuit any attempt to infer the attribute.

Of course that could just be one more bit of behavior, with the identity of the 
sugar attribute a hardcoded function of the effect type.

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] nolock/noalloc attributes (PR #84983)

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


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect &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() + "))";
+}

dougsonos wrote:

TypePrinter.cpp
I thought about using the square bracket spelling but two (admittedly dumb) 
things discouraged me:
- in the Clang branch where I originally did the work, square bracket spellings 
weren't enabled in the default C dialect
- matching square brackets (in the test that checks an AST dump) requires extra 
backslashes

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] nolock/noalloc attributes (PR #84983)

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


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect &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 
&OldFunction,
+FunctionEffectSet OldFX,
+const FunctionDecl 
&NewFunction,
+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 &OldMethod, FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod, 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,
+CalleeD

[clang] nolock/noalloc attributes (PR #84983)

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


@@ -2380,6 +2382,1239 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+// Temporary debugging option
+#define FX_ANALYZER_VERIFY_DECL_LIST 1
+
+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
+  DeclWithoutConstraintOrInference,
+
+  CallsUnsafeDecl,
+  CallsDisallowedExpr,
+};
+
+struct Diagnostic {
+  const FunctionEffect *Effect = nullptr;
+  const Decl *Callee = nullptr; // only valid for Calls*
+  SourceLocation Loc;
+  DiagnosticID ID = DiagnosticID::None;
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  Unknown,
+  Function,
+  Virtual,
+  Block
+  // unknown: probably function pointer
+};
+
+// Return whether the function 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;
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer...
+struct CallableInfo {
+  const Decl *CDecl;
+  mutable std::optional
+  MaybeName; // mutable because built on demand in const method
+  SpecialFuncType FuncType = SpecialFuncType::None;
+  FunctionEffectSet Effects;
+  CallType CType = CallType::Unknown;
+
+  CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+  : CDecl(&CD), FuncType(FT) {
+// llvm::errs() << "CallableInfo " << name() << "\n";
+
+if (auto *FD = dyn_cast(CDecl)) {
+  assert(FD->getCanonicalDecl() == FD);
+  // Use the function's definition, if any.
+  if (auto *Def = FD->getDefinition()) {
+CDecl = FD = Def;
+  }
+  CType = CallType::Function;
+  if (auto *Method = dyn_cast(FD)) {
+if (Method->isVirtual()) {
+  CType = CallType::Virtual;
+}
+  }
+  Effects = FD->getFunctionEffects();
+} else if (auto *BD = dyn_cast(CDecl)) {
+  CType = CallType::Block;
+  Effects = BD->getFunctionEffects();
+} else if (auto *VD = dyn_cast(CDecl)) {
+  // ValueDecl is function, enum, or variable, so just look at the type.
+  Effects = FunctionEffectSet::get(*VD->getType());
+}
+  }
+
+  bool isDirectCall() const {
+return CType == CallType::Function || CType == CallType::Block;
+  }
+
+  bool isVerifiable() const {
+switch (CType) {
+case CallType::Unknown:
+case CallType::Virtual:
+  break;
+case CallType::Block:
+  return true;
+case CallType::Function:
+  return functionIsVerifiable(dyn_cast(CDecl));
+}
+return false;
+  }
+
+  /// Generate a name for logging and diagnostics.
+  std::string name(Sema &Sem) const {
+if (!MaybeName) {
+  std::string Name;
+  llvm::raw_string_ostream OS(Name);
+
+  if (auto *FD = dyn_cast(CDecl)) {
+FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+  } else if (auto *BD = dyn_cast(CDecl)) {
+OS << "(block " << BD->getBlockManglingNumber() << ")";
+  } else if (auto *VD = dyn_cast(CDecl)) {
+VD->printQualifiedName(OS);
+  }
+  MaybeName = Name;
+}
+return *MaybeName;
+  }
+};
+
+// --
+// Map effects to single diagnostics.
+class EffectToDiagnosticMap {
+  // Since we currently only have a tiny number of effects (typically no more
+  // than 1), use a sorted SmallVector.
+  using Element = std::pair;
+  using ImplVec = llvm::SmallVector;
+  std::unique_ptr Impl;
+
+public:
+  Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
+if (Impl == nullptr) {
+  Impl = std::make_unique>();
+  auto &Item = Impl->emplace_back();
+  Item.first = Key;
+  return Item.second;
+}
+Element Elem(Key, {});
+auto Iter = _find(Elem);
+if (Iter != Impl->end() && Iter->first == Key) {
+  return Iter->second;
+}
+Iter = Impl->insert(Iter, Elem);
+return Iter->second;
+  }
+
+  const Diagnostic *lookup(const FunctionEffect *key) {
+if (Impl == nullptr) {
+  return nullptr;
+}
+Element elem(key, {});
+auto iter = _find(elem);
+if (iter != Im

[clang] nolock/noalloc attributes (PR #84983)

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


@@ -395,6 +395,33 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr 
&AL, unsigned ArgNum,
   return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
 }
 
+/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
+/// integer (boolean) expression. If not, emit an error and return false.
+bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
+ bool &Value) {
+  if (AL.isInvalid()) {
+return false;
+  }
+  Expr *ArgExpr = AL.getArgAsExpr(ArgNum);
+  SourceLocation ErrorLoc(AL.getLoc());
+
+  if (AL.isArgIdent(ArgNum)) {
+IdentifierLoc *IL = AL.getArgAsIdent(ArgNum);
+ErrorLoc = IL->Loc;
+  } else if (ArgExpr != nullptr) {
+if (const std::optional MaybeVal =
+ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+  Value = MaybeVal->getBoolValue();
+  return true;
+}
+  }

dougsonos wrote:

Thank you! I was looking for precedents for this and couldn't find it on any 
attributes. It bugged me that I was writing something like this.

Yes, the parameter can be an arbitrary expression.

But if it is dependent: then how would we represent `nolock(expression)` before 
we can evaluate the expression? And when would it be collapsed back into a 
concretely true or false form?

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] nolock/noalloc attributes (PR #84983)

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


@@ -339,6 +349,11 @@ namespace {
 
 bool didParseNoDeref() const { return parsedNoDeref; }
 
+void setParsedNolock(unsigned char v) { parsedNolock = v; }
+unsigned char getParsedNolock() const { return parsedNolock; }
+void setParsedNoalloc(unsigned char v) { parsedNoalloc = v; }
+unsigned char getParsedNoalloc() const { return parsedNoalloc; }

dougsonos wrote:

Yeah I thought I was trying to hide implementation details but this struct is 
local to the (giant) source file and it would definitely be better to just use 
the enum everywhere.

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] nolock/noalloc attributes (PR #84983)

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


@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+void unannotated(void);
+void nolock(void) [[clang::nolock]];

dougsonos wrote:

Wow, I never thought I'd see this day!

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] nolock/noalloc attributes (PR #84983)

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


@@ -0,0 +1,84 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s

dougsonos wrote:

Thanks, yes, I will merge them (other than the one that is checking the AST 
dump).

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] nolock/noalloc attributes (PR #84983)

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


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}

dougsonos wrote:

Since `nolock(true)` needs to be part of a type, it becomes a `FunctionEffect` 
in a `FunctionEffectSet` attached to a `FunctionProtoType`. I looked at ways to 
defer `nolock(false)` off of the type and onto the `Decl`, but this violates a 
hard rule about placement of square-bracket attributes. So `nolock(false)` is 
implemented as type sugar, via `AttributedType`, even though it's ignored on 
types and only has meaning when attached to Decls.

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] nolock/noalloc attributes (PR #84983)

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


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;

dougsonos wrote:

I decided I preferred inconsistency in DiagnosticSemaKinds.td than in the code 
that emits the diags:

```
S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
<< effectName << TopFuncName;
...
S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
<< effectName << CalleeName;
```

But maybe that's just a bit precious. As I was trying to word the diagnostics, 
there were times when they didn't always come out in consistent orders and I 
had bugs in passing the parameters in the right order.

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] nolock/noalloc attributes (PR #84983)

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


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;

dougsonos wrote:

Another way to put it: when one reads through the ~100 lines of diagnostics in 
DiagnosticSemaKinds.td, I find it helpful that %0 is always the name of the 
effect and %1 is always a function (or block) name.

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] nolock/noalloc attributes (PR #84983)

2024-03-12 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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -395,6 +395,33 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr 
&AL, unsigned ArgNum,
   return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
 }
 
+/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
+/// integer (boolean) expression. If not, emit an error and return false.
+bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
+ bool &Value) {
+  if (AL.isInvalid()) {
+return false;
+  }
+  Expr *ArgExpr = AL.getArgAsExpr(ArgNum);
+  SourceLocation ErrorLoc(AL.getLoc());
+
+  if (AL.isArgIdent(ArgNum)) {
+IdentifierLoc *IL = AL.getArgAsIdent(ArgNum);
+ErrorLoc = IL->Loc;
+  } else if (ArgExpr != nullptr) {
+if (const std::optional MaybeVal =
+ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+  Value = MaybeVal->getBoolValue();
+  return true;
+}
+  }

dougsonos wrote:

Wow. I'm going to have to build a test to explore this. Thanks.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos deleted 
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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}

dougsonos wrote:

The GNU syntax is more liberal, and yes, this is supported (and tested as of my 
next push):
```
// Alternate placement on the FunctionDecl
__attribute__((clang_nolock)) void nl_function();
// CHECK: FunctionDecl {{.*}} nl_function 'void () 
__attribute__((clang_nolock))'
```

One reason I prefer the square-bracket syntax is that it is not only consistent 
with `noexcept`, but it becomes practical to apply both `nolock` and `noexcept` 
with a single macro (not sure yet I want to do that to my codebase, but it's 
possible).

Another is that the placement rules seem more consistent and sensible, to me 
anyhow.

The `nolock/noalloc(true)` attributes do need to apply to function types, not 
declarations, so as to be able to analyze indirect calls. They only applies to 
declarations as a consequence of declarations having function types. 
`nolock/noalloc(false)` have to be treated the same way as the `true` form for 
parsing, but for analysis purposes, they end up being ignored until they land 
on a `Decl`, since indirect calls prevent inference regardless.

As for the rule about square brackets, in SemaType.cpp:
```
FUNCTION_TYPE_ATTRS_CASELIST:
  attr.setUsedAsTypeAttr();

  // Attributes with standard syntax have strict rules for what they
  // appertain to and hence should not use the "distribution" logic below.
  if (attr.isStandardAttributeSyntax() ||
  attr.isRegularKeywordAttribute()) {
if (!handleFunctionTypeAttr(state, attr, type, CFT)) {
  diagnoseBadTypeAttribute(state.getSema(), attr, type);
  attr.setInvalid();
}
break;
  }
```
So with square-bracket syntax, the attribute has to follow the right 
parenthesis enclosing the parameter list (and after const).

Incidentally, I realized that in Attr.td the attributes can derive from 
`TypeAttr` rather than `DeclOrTypeAttr`.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,84 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s

dougsonos wrote:

Hm, the standard diagnostic for diagnosing incompatible attributes produces an 
error, and the last-pass constraint analysis is skipped when there are errors. 
I'm going to keep those constraint analysis tests in a separate file and give 
the files more descriptive names, e.g. -parsing, -sema, -constraints.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect &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 
&OldFunction,
+FunctionEffectSet OldFX,
+const FunctionDecl 
&NewFunction,
+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 &OldMethod, FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod, 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,
+CalleeD

[clang] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}

dougsonos wrote:

I did one whole implementation with both the true/false forms of the attributes 
as `AttributedType`. Along the way I found and had to hack around two bugs 
where the sugar gets stripped, e.g. on an `auto` lambda.

I got some guidance that the `true` forms of the attributes made sense as part 
of the canonical type, so (confusingly, I know):
- the `true` attribute triggers the addition of a global instance of a 
`NoLockNoAllocEffect` into a `FunctionEffectSet` as an optional part of the 
`FunctionProtoType`. It does not currently put an attribute on the type.
- the `false` attribute uses `AttributedType`. BTW another limitation of 
`AttributedType` is that it doesn't seem to hold anything more than the 
attribute kind, so if we went that way we'd probably need 3 kinds for `nolock` 
(true, false, type-dependent expression) and 3 for `noalloc`. The 
type-dependent expression might end up needing a whole new type-sugar class.

Even if `FunctionEffect` turns out to be just some bits controlling details of 
the behavior, rather than a vtable, it's still attractive to me to encapsulate 
it into a uniqued object with methods, to centralize all those behaviors.

There's a fair amount of code prepared for the possibility of new, unrelated 
effects (e.g. "TCB + types"), anticipating that FunctionEffectSet could hold 
multiple effects. Considering for example a codebase that with several TCB's, 
they would all have the same behaviors about calls within themselves, but need 
to be considered unique effects, e.g. a function in TCB A can't call a function 
that's only part of TCB Z. This is the motivation for the FunctionEffect 
abstraction.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 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] nolock/noalloc attributes (PR #84983)

2024-03-13 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] nolock/noalloc attributes (PR #84983)

2024-03-13 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] nolock/noalloc attributes (PR #84983)

2024-03-13 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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -11100,6 +11136,48 @@ 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::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
+  if (!D->hasBody()) {
+if (const auto *FD = D->getAsFunction()) {
+  if (!FD->willHaveBody()) {
+return;
+  }
+}
+  }
+
+  if (Diags.getIgnoreAllWarnings() ||
+  (Diags.getSuppressSystemWarnings() &&
+   SourceMgr.isInSystemHeader(D->getLocation(
+return;
+
+  if (hasUncompilableErrorOccurred())
+return;
+
+  // For code in dependent contexts, we'll do this at instantiation time
+  // (??? This was copied from something else in AnalysisBasedWarnings ???)
+  if (cast(D)->isDependentContext()) {
+return;
+  }

dougsonos wrote:

Thanks. For now I've disabled this with a TODO and will see what happens when I 
next try the branch on our codebase.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,84 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s

dougsonos wrote:

OK, maybe "syntax" then...

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> Since you mention it is attached to the type, is it mangled then differently. 
> e.g.:
> Does the above f calls to 2 different functions?

This example is problematic because in this construct, `g` and `h` degenerate 
into function pointers, and inference doesn't work across function pointers. 
The RFC shows a technique for avoiding this (search for `nolock_fp`).

But let's try this:

```
template 
void f(T a) [[clang::nolock]] { a(); }

void m() {
auto g = []() [[clang::nolock]] {};
auto h = []() [[clang::nolock(false)]] {};

f(g);
f(h);
}

This showed:
- in `Sema::CheckAddCallableWithEffects`, I really do want to keep that line of 
code that skips functions in dependent contexts; otherwise `f` gets analyzed 
with some sort of placeholder type.
- there are two template instantiations of `f` (according to logging), for the 
separate lambdas
- the bug where `AttributedType` sugar gets lost on lambdas (when the 
"inferred" return type gets converted to a concrete one) happens here and the 
`nolock(false)` attribute is lost from `h`.

That bug defeats `h`'s wish to not have `nolock` inferred on it, so the 
analysis decides:
- `g` is verified safe
- `f` is verified safe
- `h` is inferred safe
- `f(h)` is verified safe
- `m` is safe

Good questions, thanks. I'm working on the others.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits


@@ -11100,6 +11136,48 @@ 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::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
+  if (!D->hasBody()) {
+if (const auto *FD = D->getAsFunction()) {
+  if (!FD->willHaveBody()) {
+return;
+  }
+}
+  }
+
+  if (Diags.getIgnoreAllWarnings() ||
+  (Diags.getSuppressSystemWarnings() &&
+   SourceMgr.isInSystemHeader(D->getLocation(
+return;
+
+  if (hasUncompilableErrorOccurred())
+return;
+
+  // For code in dependent contexts, we'll do this at instantiation time
+  // (??? This was copied from something else in AnalysisBasedWarnings ???)
+  if (cast(D)->isDependentContext()) {
+return;
+  }

dougsonos wrote:

This actually did turn out to be important -- without this check, a templated 
`nolock` function would get checked using placeholder template parameters, 
leading to spurious diagnostics.

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] nolock/noalloc attributes (PR #84983)

2024-03-13 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> > * the bug where `AttributedType` sugar gets lost on lambdas (when the 
> > "inferred" return type gets converted to a concrete one) happens here and 
> > the `nolock(false)` attribute is lost from `h`.
> 
> Opened an issue for this (#85120) because it really does seem like a bug to 
> me.

Thanks, I have a stab at a fix for that, here, somewhere... will push it soon.

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] Rough first stab at addressing #85120 (PR #85147)

2024-03-13 Thread Doug Wyatt via cfe-commits

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

I pointed out this issue in the review for nolock/noalloc, and @Sirraide opened 
#85120 

Here are some (very rough) bits of code I'd written to try to address the loss 
of type sugar, plus a subsequent crash involving lambdas (can't remember 
details now, and the crash may not exist any more on main).

Just in case it helps.

>From 613f04e311f083c129acaa4598cbfd9894fe3805 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 13 Mar 2024 16:02:14 -0700
Subject: [PATCH] Rough first stab at addressing #85120

---
 clang/include/clang/AST/ASTContext.h |  5 +++
 clang/lib/AST/ASTContext.cpp | 62 +++-
 clang/lib/Sema/SemaExpr.cpp  |  2 +-
 clang/lib/Sema/TreeTransform.h   | 61 ++-
 4 files changed, 125 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index ff6b64c7f72d57..ca90417c9bf70b 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1301,6 +1301,11 @@ class ASTContext : public RefCountedBase {
   /// Change the result type of a function type once it is deduced.
   void adjustDeducedFunctionResultType(FunctionDecl *FD, QualType ResultType);
 
+  /// Transform a function type to have the provided result type, preserving
+  /// AttributedType and MacroQualifiedType sugar.
+  QualType getFunctionTypeWithResultType(QualType OrigFuncType, QualType 
ResultType,
+const FunctionProtoType::ExtProtoInfo &EPI) const;
+
   /// Get a function type and produce the equivalent function type with the
   /// specified exception specification. Type sugar that can be present on a
   /// declaration of a function with an exception specification is permitted
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 5a8fae76a43a4d..035dc19ba7011d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3140,13 +3140,71 @@ const FunctionType 
*ASTContext::adjustFunctionType(const FunctionType *T,
   return cast(Result.getTypePtr());
 }
 
+// EPI is provided by the caller because in the case of 
adjustDeducedFunctionResultType, it
+// is copied entirely from the previous FunctionType, but with a block 
(ActOnBlockStmtExpr),
+// it is more complicated...
+QualType ASTContext::getFunctionTypeWithResultType(QualType OrigFuncType, 
QualType ResultType,
+  const FunctionProtoType::ExtProtoInfo &EPI) const
+{
+  // Might be wrapped in a macro qualified type.
+  if (const auto *MQT = dyn_cast(OrigFuncType)) {
+return getMacroQualifiedType(
+getFunctionTypeWithResultType(MQT->getUnderlyingType(), ResultType, 
EPI),
+MQT->getMacroIdentifier());
+  }
+
+  // Might have a calling-convention attribute.
+  if (const auto *AT = dyn_cast(OrigFuncType)) {
+return getAttributedType(
+AT->getAttrKind(),
+getFunctionTypeWithResultType(AT->getModifiedType(), ResultType, EPI),
+getFunctionTypeWithResultType(AT->getEquivalentType(), ResultType, 
EPI));
+  }
+  
+  // Anything else must be a function type. Rebuild it with the new return 
value.
+  const auto *FPT = OrigFuncType->castAs();
+  return getFunctionType(ResultType, FPT->getParamTypes(), EPI);
+}
+
+#if 0
+static void examineType(const char* prefix, QualType QT, const char* term)
+{
+  llvm::outs() << prefix;
+  if (const auto *MQT = dyn_cast(QT)) {
+examineType( "MacroQualifiedType <", MQT->getUnderlyingType(), ">");
+  } else if (const auto *AT = dyn_cast(QT)) {
+examineType("AttributedType <", AT->getEquivalentType(), ">");
+  } else {
+const auto *FPT = QT->castAs();
+assert(FPT);
+llvm::outs() << QT;
+  }
+  llvm::outs() << term;
+}
+#endif
+
 void ASTContext::adjustDeducedFunctionResultType(FunctionDecl *FD,
  QualType ResultType) {
   FD = FD->getMostRecentDecl();
   while (true) {
-const auto *FPT = FD->getType()->castAs();
+QualType OrigFuncType = FD->getType();
+const auto *FPT = OrigFuncType->castAs();
 FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
-FD->setType(getFunctionType(ResultType, FPT->getParamTypes(), EPI));
+#if 1 // my new way
+QualType NewFuncType = getFunctionTypeWithResultType(OrigFuncType, 
ResultType, EPI);
+#else // original way
+QualType NewFuncType = getFunctionType(ResultType, FPT->getParamTypes(), 
EPI);
+#endif
+/*llvm::outs() << "transform " << OrigFuncType << " -> " << NewFuncType << 
"\n";
+llvm::outs() << " isConstQualified " << OrigFuncType.isConstQualified() << 
NewFuncType.isConstQualified() << "\n";
+llvm::outs() << " isLocalConstQualified " << 
OrigFuncType.isLocalConstQualified() << NewFuncType.isLocalConstQualified() << 
"\n";
+llvm::outs() << " const method " << FPT->isConst() << 
NewFuncType->castAs()->isConst() << "\n";
+llvm::outs() << " canonical " << Ne

[clang] Rough first stab at addressing #85120 (PR #85147)

2024-03-13 Thread Doug Wyatt via cfe-commits

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


[clang] Rough first stab at addressing #85120 (PR #85147)

2024-03-13 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> @dougsonos I may have some time to look into this since there are probably 
> (as always) some annoying edge cases—particularly w/ templates. Would you be 
> fine with me committing to this branch or would it be easier for you if I 
> made a separate branch for that? Either is fine by me.

It might be simplest if you make your own branch, borrowing whatever from mine 
helps. Thanks.

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


[clang] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}

dougsonos wrote:

> The attribute itself appears to usually be stored in an AttributedTypeLoc 
> rather than as part of the type itself

Ah right, I remember now. I think my difficulty with getting to the attribute 
itself stemmed from `Expr` only having a `QualType`, no `TypeSourceInfo`. And 
for example, `TypePrinter` only has a `QualType` and thus can't print the 
details of an `Attr::AnnotateType`.

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] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;

dougsonos wrote:

Thanks. I think I added function names to diagnostics as a hack around the 
difficulty with template instantiation notes (which we discussed later in this 
file). Once that's resolved I should be able to remove them here.

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] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits


@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function '%1' must not throw or catch exceptions">,
+  InGroup;
+
+def note_func_effect_throws_or_catches : Note<
+  "'%1' cannot be inferred '%0' because it throws or catches exceptions">;
+
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function '%1' must not have static locals">,
+  InGroup;
+
+def note_func_effect_has_static_local : Note<
+  "'%1' cannot be inferred '%0' because it has a static local">;
+
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function '%1' must not use thread-local variables">,
+  InGroup;
+
+def note_func_effect_uses_thread_local : Note<
+  "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function '%1' must not access an ObjC method or property">,
+  InGroup;
+
+def note_func_effect_calls_objc : Note<
+  "'%1' cannot be inferred '%0' because it accesses an ObjC method or 
property">;
+
+def warn_func_effect_calls_disallowed_func : Warning<
+  "'%0' function '%1' must not call non-'%0' function '%2'">,
+  InGroup;
+
+// UNTESTED
+def warn_func_effect_calls_disallowed_expr : Warning<
+  "'%0' function '%1' must not call non-'%0' expression">,
+  InGroup;
+
+def note_func_effect_calls_disallowed_func : Note<
+  "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+
+def note_func_effect_call_extern : Note<
+  "'%1' cannot be inferred '%0' because it has no definition in this 
translation unit">;
+
+def note_func_effect_call_not_inferrable : Note<
+  "'%1' does not permit inference of '%0'">;
+
+def note_func_effect_call_virtual : Note<
+  "'%1' cannot be inferred '%0' because it is virtual">;
+
+def note_func_effect_call_func_ptr : Note<
+  "'%1' cannot be inferred '%0' because it is a function pointer">;
+
+// TODO: Not currently being generated
+// def warn_perf_annotation_implies_noexcept : Warning<
+//  "'%0' function should be declared noexcept">,
+//  InGroup;
+
+// TODO: Not currently being generated
+def warn_func_effect_false_on_type : Warning<
+  "only functions/methods/blocks may be declared %0(false)">,
+  InGroup;
+
+// TODO: can the usual template expansion notes be used?
+def note_func_effect_from_template : Note<
+  "in template expansion here">;

dougsonos wrote:

Right, the difficulty is that the analysis currently happens at the very end of 
Sema. Sema saves up all the `FunctionDecl` and `BlockDecl`'s to be checked, 
then at the end (AnalysisBasedWarnings), makes a pass through that list, only 
doing an AST traversal of those functions to be verified -- the list of which 
grows according to the need for inference. In that context I haven't worked out 
a way to obtain template instantiation notes for a given `FunctionDecl`.

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] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits


@@ -3922,6 +3922,42 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, 
NamedDecl *&OldD, Scope *S,
 return true;
   }
 
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+  if (OldFX != NewFX) {
+const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+for (const auto &Item : Diffs) {
+  const FunctionEffect *Effect = Item.first;
+  const bool Adding = Item.second;
+  if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+Diag(New->getLocation(),
+ diag::warn_mismatched_func_effect_redeclaration)
+<< Effect->name();
+Diag(Old->getLocation(), diag::note_previous_declaration);
+  }
+}
+
+const auto MergedFX = OldFX | NewFX;
+
+// Having diagnosed any problems, prevent further errors by applying the
+// merged set of effects to both declarations.
+auto applyMergedFX = [&](FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs();
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+  EPI.FunctionEffects = MergedFX;
+  QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+   FPT->getParamTypes(), EPI);
+
+  FD->setType(ModQT);
+};
+
+applyMergedFX(Old);
+applyMergedFX(New);
+
+OldQType = Old->getType();

dougsonos wrote:

In prototyping the feature, it was really helpful to be able to redeclare 
things like `pthread_self()` as `nolock`. This was also envisioned as a viable 
bootstrapping technique.

Maybe, however, this is an undesirable hack. A workaround would be to create 
wrapper functions that are declared safe but call the unsafe function with 
diagnostics disabled. To facilitate this a bit more cleanly, we could add the 
concept of a "leaf" function (as with TCB). `nolock_leaf` or `nolock(leaf)` (?) 
could mean "tell my callers I am `nolock`, but don't verify me."

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] nolock/noalloc attributes (PR #84983)

2024-03-14 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] [Clang] Bugfixes and improved support for `AttributedType`s in lambdas (PR #85325)

2024-03-14 Thread Doug Wyatt via cfe-commits


@@ -3140,13 +3140,35 @@ const FunctionType 
*ASTContext::adjustFunctionType(const FunctionType *T,
   return cast(Result.getTypePtr());
 }
 
+QualType ASTContext::adjustFunctionResultType(QualType FunctionType,
+  QualType ResultType) {
+  // Might be wrapped in a macro qualified type.
+  if (const auto *MQT = dyn_cast(FunctionType)) {
+return getMacroQualifiedType(
+adjustFunctionResultType(MQT->getUnderlyingType(), ResultType),
+MQT->getMacroIdentifier());
+  }
+
+  // Might have a calling-convention attribute.
+  if (const auto *AT = dyn_cast(FunctionType)) {
+return getAttributedType(
+AT->getAttrKind(),
+adjustFunctionResultType(AT->getModifiedType(), ResultType),
+adjustFunctionResultType(AT->getEquivalentType(), ResultType));
+  }
+
+  // Anything else must be a function type. Rebuild it with the new return
+  // value.

dougsonos wrote:

> Also, I just noticed, this code seems to be modelled after 
> `getFunctionTypeWithExceptionSpec()`, which is like two functions down in the 
> same file and which does pretty much the same thing that this is doing here. 
> It might make sense to merge the two.

I was hacking just enough to make it work, apologies.

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


[clang] [Clang] Bugfixes and improved support for `AttributedType`s in lambdas (PR #85325)

2024-03-14 Thread Doug Wyatt via cfe-commits

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


[clang] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> > * Conversions between functions / function pointers with and without the 
> > attribute work in C++ but not in C and I'm a bit lost (there's a bunch of 
> > debug logging around this e.g. Sema::IsFunctionConversion and 
> > Sema::ImpCastExprToType)

I've probably been staring at this way too long, but here's what's going on. My 
test is:

```
void nolock(int) [[clang::nolock]];
void x() {
void (*fp_plain)(int);
fp_plain = nolock;
}
```

At the bottom of `checkPointerTypesForAssignment`:
```
  llvm::outs() << "checkPointerTypesForAssignment calling IsFunctionConversion 
LHS " << ltrans << "   RHS " << rtrans << "\n";
  if (!S.getLangOpts().CPlusPlus &&
  S.IsFunctionConversion(ltrans, rtrans, ltrans))
```
This prints `LHS void (int)   RHS void (int) __attribute__((clang_nolock))`

Then inside isFunctionConversion, I immediately log:
```
  llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType
   << "\n";
```
and it's `void (int) -> void (int) __attribute__((clang_nolock))`

Reconciliation of the FunctionEffectSets on the two types is needed, but I'm 
confused right from the beginning here; the naming of "From" and "To" seems 
backwards. Would appreciate any pointers.

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] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> > have you tried maybe simply not setting `Changed` to `true` (perhaps only 
> > in C mode)?
> 
> It’s worth pointing out that C also warns (and C23 straight-up _errors_) if 
> you replace `nolock` w/ `noreturn` iirc, so either that’s also a bug or 
> that’s intended; I think I was going to ask Aaron about this as he’s the C 
> expert, but I forgot about that too, again because of the `AttributedType` 
> stuff.

I was testing with `noreturn` earlier. It's hard to compare, because (at least 
in C23):
```
../clang/test/Sema/attr-nolock-wip.cpp:15:19: error: 'noreturn' attribute 
cannot be applied to types
   15 | void noret(int) [[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] nolock/noalloc attributes (PR #84983)

2024-03-14 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> > have you tried maybe simply not setting `Changed` to `true` (perhaps only 
> > in C mode)?

Just tried. In C mode, it works to do nothing about effect changes in 
isFunctionConversion (!).

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] nolock/noalloc attributes (PR #84983)

2024-03-15 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> I would maybe try going with that then for now (and maybe add a comment about 
> that too); I’m not sure my function pointer example is really the same 
> situation, but I remember finding an example that was analogous but for 
> `noreturn` _somwhere_ in the test cases, but I don’t remember where.

Thanks. I just opened #85415 about the apparently reversed parameters to 
`IsFunctionConversion`

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] nolock/noalloc attributes (PR #84983)

2024-03-15 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] nolock/noalloc attributes (PR #84983)

2024-03-15 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] nolock/noalloc attributes (PR #84983)

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


@@ -3922,6 +3922,42 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, 
NamedDecl *&OldD, Scope *S,
 return true;
   }
 
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+  if (OldFX != NewFX) {
+const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+for (const auto &Item : Diffs) {
+  const FunctionEffect *Effect = Item.first;
+  const bool Adding = Item.second;
+  if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+Diag(New->getLocation(),
+ diag::warn_mismatched_func_effect_redeclaration)
+<< Effect->name();
+Diag(Old->getLocation(), diag::note_previous_declaration);
+  }
+}
+
+const auto MergedFX = OldFX | NewFX;
+
+// Having diagnosed any problems, prevent further errors by applying the
+// merged set of effects to both declarations.
+auto applyMergedFX = [&](FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs();
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+  EPI.FunctionEffects = MergedFX;
+  QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+   FPT->getParamTypes(), EPI);
+
+  FD->setType(ModQT);
+};
+
+applyMergedFX(Old);
+applyMergedFX(New);
+
+OldQType = Old->getType();

dougsonos wrote:

> > A workaround would be to create wrapper functions that are declared safe 
> > but call the unsafe function with diagnostics disabled.
> 
> I could see there being situations where you might want to be able to do 
> something like that (i.e. declare a function as safe even if the compiler 
> might think it’s unsafe because it calls unsafe functions), but I wonder if 
> you couldn’t just disable the diagnostics locally in that function (or even 
> just parts thereof) only using a `#pragma`.

Yes, though that could make it difficult to distinguish between
- calls that are truly unsafe but where disabling warnings is needed in the 
short term
- calls that are truly safe but where annotation hasn't yet caught up (e.g. 
https://developer.apple.com/documentation/kernel/1532191-vdsp_vadd )

On the other hand, it is practical to disable diagnostics through a macro 
including pragmas. The user could employ two different macros to disable 
diagnostics, and the macro names would express the difference between the two 
situations.

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] nolock/noalloc attributes (PR #84983)

2024-03-16 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] nolock/noalloc attributes (PR #84983)

2024-03-16 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] nolock/noalloc attributes (PR #84983)

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


@@ -3922,6 +3922,42 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, 
NamedDecl *&OldD, Scope *S,
 return true;
   }
 
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+  if (OldFX != NewFX) {
+const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+for (const auto &Item : Diffs) {
+  const FunctionEffect *Effect = Item.first;
+  const bool Adding = Item.second;
+  if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+Diag(New->getLocation(),
+ diag::warn_mismatched_func_effect_redeclaration)
+<< Effect->name();
+Diag(Old->getLocation(), diag::note_previous_declaration);
+  }
+}
+
+const auto MergedFX = OldFX | NewFX;
+
+// Having diagnosed any problems, prevent further errors by applying the
+// merged set of effects to both declarations.
+auto applyMergedFX = [&](FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs();
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+  EPI.FunctionEffects = MergedFX;
+  QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+   FPT->getParamTypes(), EPI);
+
+  FD->setType(ModQT);
+};
+
+applyMergedFX(Old);
+applyMergedFX(New);
+
+OldQType = Old->getType();

dougsonos wrote:

> it’s better to avoid modifying the `Old` declaration

OK, I agree that that will make more sense.

> look at all declarations of a function (starting at the most recent one, 
> because that one will most likely have the attribute on it) 

There are quite a number of places that ask this question but I'll work through 
this. This is an interesting example:
```
void foo() {
auto* x = new int;
}
void foo() [[clang::nolock]];
void bar() [[clang::nolock]] {
foo();
}
```
I think that `foo()` ought to get verified because by the time verification 
happens, the redeclaration has been seen.

I'm having difficulty testing this though, because now that the `Old` 
declaration is untouched, `MergeFunctionDecl` is unhappy that `New` has a 
different canonical type. Would it make sense to add another check near the end 
like this one?
```
  // Check if the function types are compatible when pointer size address
  // spaces are ignored.
  if (Context.hasSameFunctionTypeIgnoringPtrSizes(OldQType, NewQType))
return false;
```

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] nolock/noalloc attributes (PR #84983)

2024-03-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] nolock/noalloc attributes (PR #84983)

2024-03-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] nolock/noalloc attributes (PR #84983)

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


@@ -3922,6 +3922,42 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, 
NamedDecl *&OldD, Scope *S,
 return true;
   }
 
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+  if (OldFX != NewFX) {
+const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+for (const auto &Item : Diffs) {
+  const FunctionEffect *Effect = Item.first;
+  const bool Adding = Item.second;
+  if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+Diag(New->getLocation(),
+ diag::warn_mismatched_func_effect_redeclaration)
+<< Effect->name();
+Diag(Old->getLocation(), diag::note_previous_declaration);
+  }
+}
+
+const auto MergedFX = OldFX | NewFX;
+
+// Having diagnosed any problems, prevent further errors by applying the
+// merged set of effects to both declarations.
+auto applyMergedFX = [&](FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs();
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+  EPI.FunctionEffects = MergedFX;
+  QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+   FPT->getParamTypes(), EPI);
+
+  FD->setType(ModQT);
+};
+
+applyMergedFX(Old);
+applyMergedFX(New);
+
+OldQType = Old->getType();

dougsonos wrote:

I've made `Sema::MergeFunctionDecl` use `OldQTypeForComparison` earlier so as 
to ignore effect differences in redeclarations, and 
`FunctionDecl::getFunctionEffects()` now collects and returns the union of 
effects present in all redeclarations.

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] nolock/noalloc attributes (PR #84983)

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


@@ -2380,6 +2382,1239 @@ class UnsafeBufferUsageReporter : public 
UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// 
=
+
+// Temporary debugging option
+#define FX_ANALYZER_VERIFY_DECL_LIST 1
+
+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
+  DeclWithoutConstraintOrInference,
+
+  CallsUnsafeDecl,
+  CallsDisallowedExpr,
+};
+
+struct Diagnostic {
+  const FunctionEffect *Effect = nullptr;
+  const Decl *Callee = nullptr; // only valid for Calls*
+  SourceLocation Loc;
+  DiagnosticID ID = DiagnosticID::None;
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+ const Decl *Callee = nullptr)
+  : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  Unknown,
+  Function,
+  Virtual,
+  Block
+  // unknown: probably function pointer
+};
+
+// Return whether the function 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;
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer...
+struct CallableInfo {
+  const Decl *CDecl;
+  mutable std::optional
+  MaybeName; // mutable because built on demand in const method
+  SpecialFuncType FuncType = SpecialFuncType::None;
+  FunctionEffectSet Effects;
+  CallType CType = CallType::Unknown;
+
+  CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+  : CDecl(&CD), FuncType(FT) {
+// llvm::errs() << "CallableInfo " << name() << "\n";
+
+if (auto *FD = dyn_cast(CDecl)) {
+  assert(FD->getCanonicalDecl() == FD);
+  // Use the function's definition, if any.
+  if (auto *Def = FD->getDefinition()) {
+CDecl = FD = Def;
+  }
+  CType = CallType::Function;
+  if (auto *Method = dyn_cast(FD)) {
+if (Method->isVirtual()) {
+  CType = CallType::Virtual;
+}
+  }
+  Effects = FD->getFunctionEffects();
+} else if (auto *BD = dyn_cast(CDecl)) {
+  CType = CallType::Block;
+  Effects = BD->getFunctionEffects();
+} else if (auto *VD = dyn_cast(CDecl)) {
+  // ValueDecl is function, enum, or variable, so just look at the type.
+  Effects = FunctionEffectSet::get(*VD->getType());
+}
+  }
+
+  bool isDirectCall() const {
+return CType == CallType::Function || CType == CallType::Block;
+  }
+
+  bool isVerifiable() const {
+switch (CType) {
+case CallType::Unknown:
+case CallType::Virtual:
+  break;
+case CallType::Block:
+  return true;
+case CallType::Function:
+  return functionIsVerifiable(dyn_cast(CDecl));
+}
+return false;
+  }
+
+  /// Generate a name for logging and diagnostics.
+  std::string name(Sema &Sem) const {
+if (!MaybeName) {
+  std::string Name;
+  llvm::raw_string_ostream OS(Name);
+
+  if (auto *FD = dyn_cast(CDecl)) {
+FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+ /*Qualified=*/true);
+  } else if (auto *BD = dyn_cast(CDecl)) {
+OS << "(block " << BD->getBlockManglingNumber() << ")";
+  } else if (auto *VD = dyn_cast(CDecl)) {
+VD->printQualifiedName(OS);
+  }
+  MaybeName = Name;
+}
+return *MaybeName;
+  }
+};
+
+// --
+// Map effects to single diagnostics.
+class EffectToDiagnosticMap {
+  // Since we currently only have a tiny number of effects (typically no more
+  // than 1), use a sorted SmallVector.

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] nolock/noalloc attributes (PR #84983)

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


@@ -4912,3 +4922,279 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   FunctionEffectSet NewFX) const {
+  return false;
+}
+
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+const CXXMethodDecl &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &NoLockNoAllocEffect::nolock_instance() {
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect &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 
&OldFunction,
+FunctionEffectSet OldFX,
+const FunctionDecl 
&NewFunction,
+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 &OldMethod, FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod, 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,
+CalleeD

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

2024-04-10 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 idea here was that since the set elements are `ArrayRef`s pointing to 
separately allocated memory, the destructor of this object could do all the 
destruction. Moot if this uniquing business goes away though.

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-04-10 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;

dougsonos wrote:

Thanks. It turns out to be unused, an artifact of a previous implementation.

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-04-10 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:

I think this is a consequence of trying to abstract the concept of an effect, 
and hide the differences that make one effect different from another. You might 
remember that the original implementation had effects as basically an object 
with a vtable.

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-04-10 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 &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ 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 &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &LHS, const FunctionEffect &RHS) 
{
+return LHS.Type_ == RHS.Type_;
+  }
+  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.Type_ != RHS.Type_;
+  }
+  fr

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

2024-04-10 Thread Doug Wyatt via cfe-commits


@@ -10798,6 +10798,95 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,

dougsonos wrote:

Here, I have removed the function name, and '%0' is the name of the effect. So 
you'd see

'nonblocking' function must not allocate or deallocate memory


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-04-10 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:

Yeah this is a hard area for me with names.

What it's trying to convey is that it might, depending on some checks, add the 
callable to a list of decls to verify.

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-04-10 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-04-10 Thread Doug Wyatt via cfe-commits


@@ -5016,3 +5024,254 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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,

dougsonos wrote:

Thanks, will rename all of the `diagnoseX` methods to `shouldDiagnoseX`.

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-04-10 Thread Doug Wyatt via cfe-commits


@@ -5016,3 +5024,254 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, 
const ASTContext &Context) {
   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 &Effect : 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 &OldFunction,
+   FunctionEffectSet OldFX,
+   const FunctionDecl &NewFunction,
+   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 &OldMethod, FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod, 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 &Effect : 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 &effect) {
+  push_back(effect);
+}
+
+void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
+  const auto &Iter = 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 &Effect : RHS) {
+insert(Effect);
+  }
+  return *this;
+}
+
+FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
+  const FunctionEffectSet &LHS,
+ 

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

2024-04-10 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-04-10 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 &OldFunction,
+ FunctionEffectSet OldFX,
+ const FunctionDecl &NewFunction,
+ 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 &OldMethod,
+FunctionEffectSet OldFX,
+const CXXMethodDecl &NewMethod,
+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 &LHS, const FunctionEffect &RHS) 
{
+return LHS.Type_ == RHS.Type_;
+  }
+  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.Type_ != RHS.Type_;
+  }
+  fr

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

2024-04-10 Thread Doug Wyatt via cfe-commits


@@ -1868,6 +1868,28 @@ bool Sema::IsFunctionConversion(QualType FromType, 
QualType ToType,
   FromFn = QT->getAs();
   Changed = true;
 }
+
+if (getLangOpts().CPlusPlus) {
+  // For C, when called from checkPointerTypesForAssignment,
+  // we need not to change the type, or else even an innocuous cast
+  // like dropping effects will fail.

dougsonos wrote:

In an earlier review your suggestion was (in effect) to do nothing here for C 
while doing the type adjustment for C++.  Is there a more correct way to check 
for plain C than the absence of C++? Or is this just a matter of the comment 
not clearly describing the intent of the code? Thanks.

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-04-10 Thread Doug Wyatt via cfe-commits


@@ -7959,6 +7979,122 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr 
&Attr) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+static bool
+handleNonBlockingNonAllocatingTypeAttr(TypeProcessingState &state,
+   ParsedAttr &PAttr, QualType &type,
+   FunctionTypeUnwrapper &unwrapped) {
+  // Delay if this is not a function type.
+  if (!unwrapped.isFunctionType())
+return false;
+
+  const bool isNonBlocking = PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+ PAttr.getKind() == ParsedAttr::AT_Blocking;
+  Sema &S = state.getSema();
+
+  // Require FunctionProtoType
+  auto *FPT = unwrapped.get()->getAs();
+  if (FPT == nullptr) {
+// TODO: special diagnostic?
+return false;
+  }
+
+  bool Cond = true; // default
+
+  if (PAttr.getKind() == ParsedAttr::AT_NonBlocking ||
+  PAttr.getKind() == ParsedAttr::AT_NonAllocating) {
+// Parse the conditional expression, if any
+// TODO: There's a better way to do this. See PR feedback.
+// TODO: Handle a type-dependent expression.
+if (PAttr.getNumArgs() > 0) {
+  if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
+PAttr.setInvalid();
+return false;
+  }
+}
+  } else {
+Cond = false;
+  }
+
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+
+  auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
+Sema &S = state.getSema();
+S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
+<< attrTrue << attrFalse << false;
+// we don't necessarily have the location of the previous attribute,
+// so no note.
+PAttr.setInvalid();
+return true;
+  };
+
+  // check nonblocking(true) against blocking, and same for
+  // nonallocating
+  const BoolAttrState newState =
+  Cond ? BoolAttrState::True : BoolAttrState::False;
+  const BoolAttrState oppositeNewState =
+  Cond ? BoolAttrState::False : BoolAttrState::True;

dougsonos wrote:

Yeah I've already munged these lines...

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-04-10 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,117 @@
+// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
+
+// Make sure that the attribute gets parsed and attached to the correct AST 
elements.
+
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+// 
=
+// Square brackets, true
+
+namespace square_brackets {
+
+// On the type of the FunctionDecl
+void nl_function() [[clang::nonblocking]];
+// CHECK: FunctionDecl {{.*}} nl_function 'void () 
__attribute__((clang_nonblocking))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() [[clang::nonblocking]];
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// As an AttributedType within the nested types of a typedef
+typedef void (*nl_fp_type)() [[clang::nonblocking]];
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() 
__attribute__((clang_nonblocking))'
+using nl_fp_talias = void (*)() [[clang::nonblocking]];
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() 
__attribute__((clang_nonblocking))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On type of a FieldDecl
+struct Struct {
+   void (*nl_func_field)() [[clang::nonblocking]];
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() 
__attribute__((clang_nonblocking))'
+};
+
+// nonallocating should be subsumed into nonblocking
+void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'

dougsonos wrote:

Hm. OK. I can see two ways to accomplish this:

- store both `nonblocking` and `nonallocating` `FunctionEffects` in the 
`FunctionEffectSet` attached to the `FunctionProtoType`, despite the former 
constantly trumping the other.
- store only the `nonblocking` effect in the `FunctionEffectSet` but also 
preserve attributes with `AttributedType` sugar.

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-04-10 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> I understand that you’d want to avoid allocating memory for effects over and 
> over again, but at the same time—I think it’s fine to just don’t cache effect 
> sets at all.

I agree that this would be simpler and fine.

> Each effect has a set of properties, which are represented as flags.
...
> As I see it, either all of an effect’s properties should be modelled as 
> flags, or none should be (mixing the two sounds like the most complicated 
> approach), and if we’re doing the former, then I don’t think there is a point 
> in storing anything other than just the flags.

An effect is more than its flags: an identity, which can be `nonblocking`, 
`nonallocating` and maybe soon `tcb("name")` (In the Discourse thread, there 
were concerns about overlap with TCB, and this design really wants to support 
an improved TCB that can analyze indirect calls). In the TCB case, identity is 
all that matters, and none of the flags will matter. Identity is also the 
straightforward way to implement the concept of `nonblocking` being a superset 
of `nonallocating` in a number of places that check.

So that rules out the idea of reducing an effect to those flags.

But yes, an effect need not hold anything more than its type/identity (and for 
TCB, a "subtype" that mapped to its name/identity). The flags are commented as 
being cached in the `FunctionEffect` as a convenience/optimization since there 
is space. But they definitely don't **have** to be there and could be simply 
returned as constants, mapped from the type. I would agree that that would make 
things clearer.

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-04-10 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> > I understand that you’d want to avoid allocating memory for effects over 
> > and over again, but at the same time—I think it’s fine to just don’t cache 
> > effect sets at all.
> 
> I agree that this would be simpler and fine.
> 
> > Each effect has a set of properties, which are represented as flags.
> > ...
> > As I see it, either all of an effect’s properties should be modelled as 
> > flags, or none should be (mixing the two sounds like the most complicated 
> > approach), and if we’re doing the former, then I don’t think there is a 
> > point in storing anything other than just the flags.
> 
> An effect is more than its flags: its type is an identity, e.g. 
> `nonblocking`, `nonallocating` and maybe soon `tcb("name")` (In the Discourse 
> thread, there were concerns about overlap with TCB, and this design really 
> wants to support an improved TCB that can analyze indirect calls). In the TCB 
> case, identity is all that matters, and none of the flags will matter. 
> Identity is also the straightforward way to implement the concept of 
> `nonblocking` being a superset of `nonallocating` in a number of places that 
> check.
> 
> So that rules out the idea of reducing an effect to those flags.
> 
> But yes, an effect need not hold anything more than its type/identity (and 
> for TCB, a "subtype" that mapped to its name/identity). The flags are 
> commented as being cached in the `FunctionEffect` as a 
> convenience/optimization since there is space. But they definitely don't 
> **have** to be there and could be simply returned as constants, determined by 
> the type. I would agree that that would make things clearer.



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-04-10 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,117 @@
+// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
+
+// Make sure that the attribute gets parsed and attached to the correct AST 
elements.
+
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+// 
=
+// Square brackets, true
+
+namespace square_brackets {
+
+// On the type of the FunctionDecl
+void nl_function() [[clang::nonblocking]];
+// CHECK: FunctionDecl {{.*}} nl_function 'void () 
__attribute__((clang_nonblocking))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() [[clang::nonblocking]];
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// As an AttributedType within the nested types of a typedef
+typedef void (*nl_fp_type)() [[clang::nonblocking]];
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() 
__attribute__((clang_nonblocking))'
+using nl_fp_talias = void (*)() [[clang::nonblocking]];
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() 
__attribute__((clang_nonblocking))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On type of a FieldDecl
+struct Struct {
+   void (*nl_func_field)() [[clang::nonblocking]];
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() 
__attribute__((clang_nonblocking))'
+};
+
+// nonallocating should be subsumed into nonblocking
+void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'

dougsonos wrote:

Many function attributes (calling conventions) are only represented in the AST 
as part of `FunctionProtoType` without any source locations. That seems to 
argue for the first option.

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-04-10 Thread Doug Wyatt via cfe-commits

dougsonos wrote:

> One thing I noticed you bring up on Discourse and which I agree is probably 
> for the better: this pr can just be about introducing an effect system; 
> actually using it can be deferred to a follow-up pr. This pr is already big 
> enough if you consider just those changes, and there’s already enough to 
> consider here to the point that _also_ trying to review the analysis part of 
> this here would probably get too messy...

Yeah, when things settle down from this latest round of feedback, I'll back out 
as much as possible for "phase two".

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-04-10 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:

One way to address this would be to move all of the behavioral methods of 
`FunctionEffect` to be static methods of `Sema` which take `FunctionEffect` as 
the first parameter.

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-04-11 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 &OldFX,
+QualType NewType,
+const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl 
&OldFunction,
+   const FunctionEffectSet &OldFX,
+   const FunctionDecl &NewFunction,
+   const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method
+  /// override should generate a diagnostic.
+  OverrideResult
+  shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+   const FunctionEffectSet &OldFX,
+   const CXXMethodDecl &NewMethod,
+   const FunctionEffectSet &NewFX) const;
+
+  /// Return true if the effect is allowed to be inferred on the callee,
+  /// which is either a FunctionDecl or BlockDecl.
+  /// This is only used if the effect has FE_InferrableOnCallees flag set.
+  /// Example: This allows nonblocking(false) to prevent inference for the
+  /// function.
+  bool canInferOnFunction(const Decl &Callee) const;
+
+  // Return false for success. When true is returned for a direct call, then 
the
+  // FE_InferrableOnCallees flag may trigger inference rather than an immediate
+  // diagnostic. Caller should be assumed to have the effect (it may not have 
it
+  // explicitly when inferring).
+  bool shouldDiagnoseFunctionCall(bool Direct,
+  const FunctionEffectSet &CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.FKind != RHS.

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

2024-04-11 Thread Doug Wyatt via cfe-commits


@@ -3637,6 +3637,15 @@ FunctionProtoType::FunctionProtoType(QualType result, 
ArrayRef params,
 auto &EllipsisLoc = *getTrailingObjects();
 EllipsisLoc = epi.EllipsisLoc;
   }
+
+  if (epi.FunctionEffects) {
+auto &ExtraBits = *getTrailingObjects();
+ExtraBits.HasFunctionEffects = true;
+
+// N.B. This is uninitialized storage.
+FunctionEffectSet *PFX = getTrailingObjects();
+new (PFX) FunctionEffectSet(epi.FunctionEffects);

dougsonos wrote:

It would also appear that neither `FunctionProtoType` nor `TrailingObjects` has 
any provision for invoking the destructors of the trailing objects. Is it okay 
that they are leaked?

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-04-11 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,190 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) 
currently prevent

dougsonos wrote:

Split the file.

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-04-11 Thread Doug Wyatt via cfe-commits


@@ -1868,6 +1868,28 @@ bool Sema::IsFunctionConversion(QualType FromType, 
QualType ToType,
   FromFn = QT->getAs();
   Changed = true;
 }
+
+if (getLangOpts().CPlusPlus) {
+  // For C, when called from checkPointerTypesForAssignment,
+  // we need not to change the type, or else even an innocuous cast
+  // like dropping effects will fail.

dougsonos wrote:

Cleaned this up

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-04-11 Thread Doug Wyatt via cfe-commits


@@ -18324,6 +18324,47 @@ bool Sema::CheckOverridingFunctionAttributes(const 
CXXMethodDecl *New,
 return true;
   }
 
+  // Virtual overrides: check for matching effects.
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+
+  if (OldFX != NewFX) {
+const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+bool AnyDiags = false;
+
+for (const auto &Item : Diffs) {
+  const FunctionEffect &Effect = Item.first;
+  const bool Adding = Item.second;
+  switch (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) 
{
+  case FunctionEffect::OverrideResult::Ignore:
+break;
+  case FunctionEffect::OverrideResult::Warn:
+Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
+<< Effect.name();
+Diag(Old->getLocation(), diag::note_overridden_virtual_function);
+// TODO: It would be nice to have a FIXIT here!
+AnyDiags = true;
+break;
+  case FunctionEffect::OverrideResult::Propagate: {
+auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
+
+FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
+EPI.FunctionEffects = MergedFX;
+QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
+ NewFT->getParamTypes(), EPI);
+
+// TODO: It's ugly to be mutating the incoming const method. It is
+// mutable in the calling function, though. There is also the
+// possibility here that we are discarding some other sort of sugar on
+// the method's type.
+const_cast(New)->setType(ModQT);

dougsonos wrote:

Did that

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-04-11 Thread Doug Wyatt via cfe-commits


@@ -0,0 +1,117 @@
+// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
+
+// Make sure that the attribute gets parsed and attached to the correct AST 
elements.
+
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+// 
=
+// Square brackets, true
+
+namespace square_brackets {
+
+// On the type of the FunctionDecl
+void nl_function() [[clang::nonblocking]];
+// CHECK: FunctionDecl {{.*}} nl_function 'void () 
__attribute__((clang_nonblocking))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() [[clang::nonblocking]];
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() [[clang::nonblocking]]);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// As an AttributedType within the nested types of a typedef
+typedef void (*nl_fp_type)() [[clang::nonblocking]];
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() 
__attribute__((clang_nonblocking))'
+using nl_fp_talias = void (*)() [[clang::nonblocking]];
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() 
__attribute__((clang_nonblocking))'
+
+// From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() 
__attribute__((clang_nonblocking))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() 
__attribute__((clang_nonblocking))'
+
+// On type of a FieldDecl
+struct Struct {
+   void (*nl_func_field)() [[clang::nonblocking]];
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() 
__attribute__((clang_nonblocking))'
+};
+
+// nonallocating should be subsumed into nonblocking
+void nl1() [[clang::nonblocking]] [[clang::nonallocating]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nonblocking))'

dougsonos wrote:

Now both effects remain present in the `FunctionProtoType`

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-04-12 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 &OldFX,
+QualType NewType,
+const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool shouldDiagnoseRedeclaration(bool Adding, const FunctionDecl 
&OldFunction,
+   const FunctionEffectSet &OldFX,
+   const FunctionDecl &NewFunction,
+   const FunctionEffectSet &NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method
+  /// override should generate a diagnostic.
+  OverrideResult
+  shouldDiagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+   const FunctionEffectSet &OldFX,
+   const CXXMethodDecl &NewMethod,
+   const FunctionEffectSet &NewFX) const;
+
+  /// Return true if the effect is allowed to be inferred on the callee,
+  /// which is either a FunctionDecl or BlockDecl.
+  /// This is only used if the effect has FE_InferrableOnCallees flag set.
+  /// Example: This allows nonblocking(false) to prevent inference for the
+  /// function.
+  bool canInferOnFunction(const Decl &Callee) const;
+
+  // Return false for success. When true is returned for a direct call, then 
the
+  // FE_InferrableOnCallees flag may trigger inference rather than an immediate
+  // diagnostic. Caller should be assumed to have the effect (it may not have 
it
+  // explicitly when inferring).
+  bool shouldDiagnoseFunctionCall(bool Direct,
+  const FunctionEffectSet &CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.FKind == RHS.FKind;
+  }
+  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) 
{
+return LHS.FKind != RHS.

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

2024-04-12 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-04-12 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-04-24 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos deleted 
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-04-24 Thread Doug Wyatt via cfe-commits

https://github.com/dougsonos deleted 
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] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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

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

This addresses an issue where the explicit alignment of 2 (for C++ ABI reasons) 
was being propagated to the back end and causing under-aligned functions (in 
special sections). (#90358)

This is an alternate approach suggested by @efriedma-quic in PR #90415.

(There are a few failing tests. One I looked at was checking for the explicit 
alignment of 2, which no longer appears in AArch64 due to this change.)


>From 4c312af7af5d0e419269c5b2304b0f898363bb85 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Tue, 30 Apr 2024 21:43:16 -0700
Subject: [PATCH] In AArch64's DataLayout, specify a minimum function alignment
 of 4. This addresses an issue where the explicit alignment of 2 (for C++ ABI
 reasons) was being propagated to the back end and causing under-aligned
 functions (in special sections). (#90358)

---
 clang/lib/Basic/Targets/AArch64.cpp  | 12 ++--
 llvm/lib/IR/AutoUpgrade.cpp  |  8 
 llvm/lib/Target/AArch64/AArch64TargetMachine.cpp |  8 
 3 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/AArch64.cpp 
b/clang/lib/Basic/Targets/AArch64.cpp
index c8d243a8fb7aea..a5dd803f636b90 100644
--- a/clang/lib/Basic/Targets/AArch64.cpp
+++ b/clang/lib/Basic/Targets/AArch64.cpp
@@ -1480,11 +1480,11 @@ AArch64leTargetInfo::AArch64leTargetInfo(const 
llvm::Triple &Triple,
 void AArch64leTargetInfo::setDataLayout() {
   if (getTriple().isOSBinFormatMachO()) {
 if(getTriple().isArch32Bit())
-  resetDataLayout("e-m:o-p:32:32-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128", "_");
 else
-  resetDataLayout("e-m:o-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-Fn32-i64:64-i128:128-n32:64-S128", "_");
   } else
-resetDataLayout("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+
resetDataLayout("e-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 void AArch64leTargetInfo::getTargetDefines(const LangOptions &Opts,
@@ -1507,7 +1507,7 @@ void AArch64beTargetInfo::getTargetDefines(const 
LangOptions &Opts,
 
 void AArch64beTargetInfo::setDataLayout() {
   assert(!getTriple().isOSBinFormatMachO());
-  resetDataLayout("E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+  resetDataLayout("E-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 WindowsARM64TargetInfo::WindowsARM64TargetInfo(const llvm::Triple &Triple,
@@ -1530,8 +1530,8 @@ WindowsARM64TargetInfo::WindowsARM64TargetInfo(const 
llvm::Triple &Triple,
 
 void WindowsARM64TargetInfo::setDataLayout() {
   resetDataLayout(Triple.isOSBinFormatMachO()
-  ? "e-m:o-i64:64-i128:128-n32:64-S128"
-  : "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128",
+  ? "e-m:o-Fn32-i64:64-i128:128-n32:64-S128"
+  : 
"e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128",
   Triple.isOSBinFormatMachO() ? "_" : "");
 }
 
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 634b2dd5119e8d..eed946dc36580e 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -5387,6 +5387,14 @@ std::string llvm::UpgradeDataLayoutString(StringRef DL, 
StringRef TT) {
 return Res;
   }
 
+  // AArch64 data layout upgrades.
+  if (T.isAArch64()) {
+// Add "-Fn32"
+if (!DL.contains("-Fn32"))
+  Res.append("-Fn32");
+return Res;
+  }
+
   if (!T.isX86())
 return Res;
 
diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp 
b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
index 7ef78cbba352a5..4ff5fb94162a93 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
@@ -275,15 +275,15 @@ static std::string computeDataLayout(const Triple &TT,
  bool LittleEndian) {
   if (TT.isOSBinFormatMachO()) {
 if (TT.getArch() == Triple::aarch64_32)
-  return "e-m:o-p:32:32-i64:64-i128:128-n32:64-S128";
-return "e-m:o-i64:64-i128:128-n32:64-S128";
+  return "e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128";
+return "e-m:o-Fn32-i64:64-i128:128-n32:64-S128";
   }
   if (TT.isOSBinFormatCOFF())
-return "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128";
+return "e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128";
   std::string Endian = LittleEndian ? "e" : "E";
   std::string Ptr32 = TT.getEnvironment() == Triple::GNUILP32 ? "-p:32:32" : 
"";
   return Endian + "-m:e" + Ptr32 +
- "-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
+ "-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
 }
 
 static StringRef computeDefaultCPU(const Triple &TT, StringRef CPU) {

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

[clang] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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


@@ -5387,6 +5387,14 @@ std::string llvm::UpgradeDataLayoutString(StringRef DL, 
StringRef TT) {
 return Res;
   }
 
+  // AArch64 data layout upgrades.
+  if (T.isAArch64()) {
+// Add "-Fn32"
+if (!DL.contains("-Fn32"))
+  Res.append("-Fn32");

dougsonos wrote:

OK, thanks. I was afraid of that. I was copying two patterns - in ARM, `Fn8` 
seemed to be after any `p` and before `i`. In this file, the modifications are 
usually (always?) appending.

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


[clang] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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

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


[clang] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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

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

>From 4c312af7af5d0e419269c5b2304b0f898363bb85 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Tue, 30 Apr 2024 21:43:16 -0700
Subject: [PATCH 1/3] In AArch64's DataLayout, specify a minimum function
 alignment of 4. This addresses an issue where the explicit alignment of 2
 (for C++ ABI reasons) was being propagated to the back end and causing
 under-aligned functions (in special sections). (#90358)

---
 clang/lib/Basic/Targets/AArch64.cpp  | 12 ++--
 llvm/lib/IR/AutoUpgrade.cpp  |  8 
 llvm/lib/Target/AArch64/AArch64TargetMachine.cpp |  8 
 3 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/AArch64.cpp 
b/clang/lib/Basic/Targets/AArch64.cpp
index c8d243a8fb7aea..a5dd803f636b90 100644
--- a/clang/lib/Basic/Targets/AArch64.cpp
+++ b/clang/lib/Basic/Targets/AArch64.cpp
@@ -1480,11 +1480,11 @@ AArch64leTargetInfo::AArch64leTargetInfo(const 
llvm::Triple &Triple,
 void AArch64leTargetInfo::setDataLayout() {
   if (getTriple().isOSBinFormatMachO()) {
 if(getTriple().isArch32Bit())
-  resetDataLayout("e-m:o-p:32:32-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128", "_");
 else
-  resetDataLayout("e-m:o-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-Fn32-i64:64-i128:128-n32:64-S128", "_");
   } else
-resetDataLayout("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+
resetDataLayout("e-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 void AArch64leTargetInfo::getTargetDefines(const LangOptions &Opts,
@@ -1507,7 +1507,7 @@ void AArch64beTargetInfo::getTargetDefines(const 
LangOptions &Opts,
 
 void AArch64beTargetInfo::setDataLayout() {
   assert(!getTriple().isOSBinFormatMachO());
-  resetDataLayout("E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+  resetDataLayout("E-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 WindowsARM64TargetInfo::WindowsARM64TargetInfo(const llvm::Triple &Triple,
@@ -1530,8 +1530,8 @@ WindowsARM64TargetInfo::WindowsARM64TargetInfo(const 
llvm::Triple &Triple,
 
 void WindowsARM64TargetInfo::setDataLayout() {
   resetDataLayout(Triple.isOSBinFormatMachO()
-  ? "e-m:o-i64:64-i128:128-n32:64-S128"
-  : "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128",
+  ? "e-m:o-Fn32-i64:64-i128:128-n32:64-S128"
+  : 
"e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128",
   Triple.isOSBinFormatMachO() ? "_" : "");
 }
 
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 634b2dd5119e8d..eed946dc36580e 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -5387,6 +5387,14 @@ std::string llvm::UpgradeDataLayoutString(StringRef DL, 
StringRef TT) {
 return Res;
   }
 
+  // AArch64 data layout upgrades.
+  if (T.isAArch64()) {
+// Add "-Fn32"
+if (!DL.contains("-Fn32"))
+  Res.append("-Fn32");
+return Res;
+  }
+
   if (!T.isX86())
 return Res;
 
diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp 
b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
index 7ef78cbba352a5..4ff5fb94162a93 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
@@ -275,15 +275,15 @@ static std::string computeDataLayout(const Triple &TT,
  bool LittleEndian) {
   if (TT.isOSBinFormatMachO()) {
 if (TT.getArch() == Triple::aarch64_32)
-  return "e-m:o-p:32:32-i64:64-i128:128-n32:64-S128";
-return "e-m:o-i64:64-i128:128-n32:64-S128";
+  return "e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128";
+return "e-m:o-Fn32-i64:64-i128:128-n32:64-S128";
   }
   if (TT.isOSBinFormatCOFF())
-return "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128";
+return "e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128";
   std::string Endian = LittleEndian ? "e" : "E";
   std::string Ptr32 = TT.getEnvironment() == Triple::GNUILP32 ? "-p:32:32" : 
"";
   return Endian + "-m:e" + Ptr32 +
- "-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
+ "-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
 }
 
 static StringRef computeDefaultCPU(const Triple &TT, StringRef CPU) {

>From 5a2b9e1b0444c52cd7f7cf9a3ddbb573403101a5 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 1 May 2024 07:27:11 -0700
Subject: [PATCH 2/3] Move "-Fn32" to the end of the data layout strings, to
 make the auto-upgrade simplest.

---
 clang/lib/Basic/Targets/AArch64.cpp  | 12 ++--
 llvm/lib/Target/AArch64/AArch64TargetMachine.cpp |  8 
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/AArch64.cpp 
b/clang/lib/Basic/Targets/AArch64.cpp
index a5dd803f636b90..1a02520d7bd1f8 100

[clang] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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

dougsonos wrote:

> I'd expect regression tests for the autoupgrade, and a test that clang isn't 
> generating the alignment markings anymore.

I just pushed:
- the `-Fn32` is at the end of the data layout strings
- the 6 tests that failed for me locally (testing only AArch64) are fixed

That includes a change to CodeGen/CodeGenCXX/member-alignment.cpp, which checks 
for alignment / no alignment on C++ methods, depending on target.

I'm not clear on when the autoupgrade runs and how to test it. Is there an 
example I can follow?

Thanks!

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


[clang] [llvm] [clang backend] In AArch64's DataLayout, specify a minimum function alignment of 4. (PR #90702)

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

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

>From 4c312af7af5d0e419269c5b2304b0f898363bb85 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Tue, 30 Apr 2024 21:43:16 -0700
Subject: [PATCH 1/4] In AArch64's DataLayout, specify a minimum function
 alignment of 4. This addresses an issue where the explicit alignment of 2
 (for C++ ABI reasons) was being propagated to the back end and causing
 under-aligned functions (in special sections). (#90358)

---
 clang/lib/Basic/Targets/AArch64.cpp  | 12 ++--
 llvm/lib/IR/AutoUpgrade.cpp  |  8 
 llvm/lib/Target/AArch64/AArch64TargetMachine.cpp |  8 
 3 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/AArch64.cpp 
b/clang/lib/Basic/Targets/AArch64.cpp
index c8d243a8fb7aea..a5dd803f636b90 100644
--- a/clang/lib/Basic/Targets/AArch64.cpp
+++ b/clang/lib/Basic/Targets/AArch64.cpp
@@ -1480,11 +1480,11 @@ AArch64leTargetInfo::AArch64leTargetInfo(const 
llvm::Triple &Triple,
 void AArch64leTargetInfo::setDataLayout() {
   if (getTriple().isOSBinFormatMachO()) {
 if(getTriple().isArch32Bit())
-  resetDataLayout("e-m:o-p:32:32-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128", "_");
 else
-  resetDataLayout("e-m:o-i64:64-i128:128-n32:64-S128", "_");
+  resetDataLayout("e-m:o-Fn32-i64:64-i128:128-n32:64-S128", "_");
   } else
-resetDataLayout("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+
resetDataLayout("e-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 void AArch64leTargetInfo::getTargetDefines(const LangOptions &Opts,
@@ -1507,7 +1507,7 @@ void AArch64beTargetInfo::getTargetDefines(const 
LangOptions &Opts,
 
 void AArch64beTargetInfo::setDataLayout() {
   assert(!getTriple().isOSBinFormatMachO());
-  resetDataLayout("E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
+  resetDataLayout("E-m:e-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128");
 }
 
 WindowsARM64TargetInfo::WindowsARM64TargetInfo(const llvm::Triple &Triple,
@@ -1530,8 +1530,8 @@ WindowsARM64TargetInfo::WindowsARM64TargetInfo(const 
llvm::Triple &Triple,
 
 void WindowsARM64TargetInfo::setDataLayout() {
   resetDataLayout(Triple.isOSBinFormatMachO()
-  ? "e-m:o-i64:64-i128:128-n32:64-S128"
-  : "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128",
+  ? "e-m:o-Fn32-i64:64-i128:128-n32:64-S128"
+  : 
"e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128",
   Triple.isOSBinFormatMachO() ? "_" : "");
 }
 
diff --git a/llvm/lib/IR/AutoUpgrade.cpp b/llvm/lib/IR/AutoUpgrade.cpp
index 634b2dd5119e8d..eed946dc36580e 100644
--- a/llvm/lib/IR/AutoUpgrade.cpp
+++ b/llvm/lib/IR/AutoUpgrade.cpp
@@ -5387,6 +5387,14 @@ std::string llvm::UpgradeDataLayoutString(StringRef DL, 
StringRef TT) {
 return Res;
   }
 
+  // AArch64 data layout upgrades.
+  if (T.isAArch64()) {
+// Add "-Fn32"
+if (!DL.contains("-Fn32"))
+  Res.append("-Fn32");
+return Res;
+  }
+
   if (!T.isX86())
 return Res;
 
diff --git a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp 
b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
index 7ef78cbba352a5..4ff5fb94162a93 100644
--- a/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
+++ b/llvm/lib/Target/AArch64/AArch64TargetMachine.cpp
@@ -275,15 +275,15 @@ static std::string computeDataLayout(const Triple &TT,
  bool LittleEndian) {
   if (TT.isOSBinFormatMachO()) {
 if (TT.getArch() == Triple::aarch64_32)
-  return "e-m:o-p:32:32-i64:64-i128:128-n32:64-S128";
-return "e-m:o-i64:64-i128:128-n32:64-S128";
+  return "e-m:o-p:32:32-Fn32-i64:64-i128:128-n32:64-S128";
+return "e-m:o-Fn32-i64:64-i128:128-n32:64-S128";
   }
   if (TT.isOSBinFormatCOFF())
-return "e-m:w-p:64:64-i32:32-i64:64-i128:128-n32:64-S128";
+return "e-m:w-p:64:64-Fn32-i32:32-i64:64-i128:128-n32:64-S128";
   std::string Endian = LittleEndian ? "e" : "E";
   std::string Ptr32 = TT.getEnvironment() == Triple::GNUILP32 ? "-p:32:32" : 
"";
   return Endian + "-m:e" + Ptr32 +
- "-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
+ "-Fn32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128";
 }
 
 static StringRef computeDefaultCPU(const Triple &TT, StringRef CPU) {

>From 5a2b9e1b0444c52cd7f7cf9a3ddbb573403101a5 Mon Sep 17 00:00:00 2001
From: Doug Wyatt 
Date: Wed, 1 May 2024 07:27:11 -0700
Subject: [PATCH 2/4] Move "-Fn32" to the end of the data layout strings, to
 make the auto-upgrade simplest.

---
 clang/lib/Basic/Targets/AArch64.cpp  | 12 ++--
 llvm/lib/Target/AArch64/AArch64TargetMachine.cpp |  8 
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/AArch64.cpp 
b/clang/lib/Basic/Targets/AArch64.cpp
index a5dd803f636b90..1a02520d7bd1f8 100

  1   2   3   4   >