https://github.com/dzbarsky created 
https://github.com/llvm/llvm-project/pull/202654

Declaration-only attributes all diagnose statement use with the same
err_decl_attribute_invalid_on_stmt implementation. Statement-only attributes
likewise share err_attribute_invalid_on_decl, but TableGen currently emits one
virtual override per attribute.

Generate DeclOnlyParsedAttrInfo and StmtOnlyParsedAttrInfo bases for
attributes with a single subject domain. Keep each attribute's check for its
supported domain and every AttrInfoMap entry unchanged. Static assertions
ensure the bases do not increase ParsedAttrInfo size.

In a Release arm64 build, ParsedAttr.cpp.o text decreases from 684,768 to
422,172 bytes (-262,596, -38.3%). Standalone clang __text decreases from
93,136,024 to 92,873,428 bytes (-262,596), raw file size decreases from
193,587,912 to 193,278,520 bytes (-309,392), and stripped file size decreases
from 171,783,328 to 171,518,448 bytes (-264,880).

In the LLVM 22 Bazel build, identical-code folding already shares the
generated bodies, so loadable sections are unchanged. Fewer local symbols
still reduce standalone clang and multicall by 43,408 bytes each. The Darwin
release-stripped Bazel outputs are unchanged.

An attribute-heavy parsing benchmark used 200,000 declarations with six
attributes and 60 alternating baseline/candidate pairs. Mean CPU time changed
from 1.793171 to 1.775268 seconds (-0.998%); the 95% paired confidence
interval was -1.788% to -0.209%.

Tests cover generated inheritance, single shared diagnostic bodies, unchanged
declaration/statement diagnostics, and representative declaration-only and
statement-only attributes. Diagnostics from the LLVM 22 baseline and
candidate are byte-identical.

Work towards #202616

>From 297b47a96ec900380922cde90e085089c3080f3e Mon Sep 17 00:00:00 2001
From: David Zbarsky <[email protected]>
Date: Tue, 9 Jun 2026 04:04:01 -0400
Subject: [PATCH] [clang][Sema] Share ParsedAttrInfo cross-domain diagnostics

Declaration-only attributes all diagnose statement use with the same
err_decl_attribute_invalid_on_stmt implementation. Statement-only attributes
likewise share err_attribute_invalid_on_decl, but TableGen currently emits one
virtual override per attribute.

Generate DeclOnlyParsedAttrInfo and StmtOnlyParsedAttrInfo bases for
attributes with a single subject domain. Keep each attribute's check for its
supported domain and every AttrInfoMap entry unchanged. Static assertions
ensure the bases do not increase ParsedAttrInfo size.

In a Release arm64 build, ParsedAttr.cpp.o text decreases from 684,768 to
422,172 bytes (-262,596, -38.3%). Standalone clang __text decreases from
93,136,024 to 92,873,428 bytes (-262,596), raw file size decreases from
193,587,912 to 193,278,520 bytes (-309,392), and stripped file size decreases
from 171,783,328 to 171,518,448 bytes (-264,880).

In the LLVM 22 Bazel build, identical-code folding already shares the
generated bodies, so loadable sections are unchanged. Fewer local symbols
still reduce standalone clang and multicall by 43,408 bytes each. The Darwin
release-stripped Bazel outputs are unchanged.

An attribute-heavy parsing benchmark used 200,000 declarations with six
attributes and 60 alternating baseline/candidate pairs. Mean CPU time changed
from 1.793171 to 1.775268 seconds (-0.998%); the 95% paired confidence
interval was -1.788% to -0.209%.

Tests cover generated inheritance, single shared diagnostic bodies, unchanged
declaration/statement diagnostics, and representative declaration-only and
statement-only attributes. Diagnostics from the LLVM 22 baseline and
candidate are byte-identical.
---
 .../SemaCXX/attr-appertains-to-domain.cpp     | 10 +++
 .../test/TableGen/attr-parsed-info-sharing.td | 53 +++++++++++
 clang/utils/TableGen/ClangAttrEmitter.cpp     | 90 ++++++++++++-------
 3 files changed, 122 insertions(+), 31 deletions(-)
 create mode 100644 clang/test/SemaCXX/attr-appertains-to-domain.cpp
 create mode 100644 clang/test/TableGen/attr-parsed-info-sharing.td

diff --git a/clang/test/SemaCXX/attr-appertains-to-domain.cpp 
b/clang/test/SemaCXX/attr-appertains-to-domain.cpp
new file mode 100644
index 0000000000000..84e4955e98b43
--- /dev/null
+++ b/clang/test/SemaCXX/attr-appertains-to-domain.cpp
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+
+void reject_declaration_attribute_on_statement() {
+  __attribute__((unused)); // expected-error {{'unused' attribute cannot be 
applied to a statement}}
+}
+
+void reject_statement_attribute_on_declaration() {
+  // expected-error@+1 {{'fallthrough' attribute cannot be applied to a 
declaration}}
+  [[fallthrough]] int value;
+}
diff --git a/clang/test/TableGen/attr-parsed-info-sharing.td 
b/clang/test/TableGen/attr-parsed-info-sharing.td
new file mode 100644
index 0000000000000..cbab9b69f3e8e
--- /dev/null
+++ b/clang/test/TableGen/attr-parsed-info-sharing.td
@@ -0,0 +1,53 @@
+// RUN: clang-tblgen -gen-clang-attr-parsed-attr-impl -I%p/../../include %s -o 
%t
+// RUN: FileCheck %s < %t
+// RUN: grep "diag::err_decl_attribute_invalid_on_stmt" %t | count 1
+// RUN: grep "diag::err_attribute_invalid_on_decl" %t | count 1
+
+include "clang/Basic/Attr.td"
+
+def TestDeclOnly : InheritableAttr {
+  let Spellings = [Clang<"test_decl_only">];
+  let Subjects = SubjectList<[Function]>;
+  let Documentation = [Undocumented];
+}
+
+def TestStmtOnly : StmtAttr {
+  let Spellings = [Clang<"test_stmt_only">];
+  let Subjects = SubjectList<[NullStmt], ErrorDiag, "empty statements">;
+  let Documentation = [Undocumented];
+}
+
+def TestDeclAndStmt : DeclOrStmtAttr {
+  let Spellings = [Clang<"test_decl_and_stmt">];
+  let Subjects =
+      SubjectList<[Function, NullStmt], ErrorDiag,
+                  "functions and empty statements">;
+  let Documentation = [Undocumented];
+}
+
+// CHECK-LABEL: struct DeclOnlyParsedAttrInfo : ParsedAttrInfo {
+// CHECK: bool diagAppertainsToStmt(Sema &S, const ParsedAttr &AL,
+// CHECK: S.Diag(AL.getLoc(), diag::err_decl_attribute_invalid_on_stmt)
+// CHECK: static_assert(sizeof(DeclOnlyParsedAttrInfo) == 
sizeof(ParsedAttrInfo));
+
+// CHECK-LABEL: struct StmtOnlyParsedAttrInfo : ParsedAttrInfo {
+// CHECK: bool diagAppertainsToDecl(Sema &S, const ParsedAttr &AL,
+// CHECK: S.Diag(AL.getLoc(), diag::err_attribute_invalid_on_decl)
+// CHECK: static_assert(sizeof(StmtOnlyParsedAttrInfo) == 
sizeof(ParsedAttrInfo));
+
+// CHECK-LABEL: struct ParsedAttrInfoTestDeclAndStmt final : public 
ParsedAttrInfo {
+// CHECK: constexpr ParsedAttrInfoTestDeclAndStmt() : ParsedAttrInfo(
+// CHECK: bool diagAppertainsToDecl(
+// CHECK: bool diagAppertainsToStmt(
+
+// CHECK-LABEL: struct ParsedAttrInfoTestDeclOnly final : public 
DeclOnlyParsedAttrInfo {
+// CHECK: constexpr ParsedAttrInfoTestDeclOnly() : DeclOnlyParsedAttrInfo(
+// CHECK: bool diagAppertainsToDecl(
+// CHECK-NOT: bool diagAppertainsToStmt(
+// CHECK: static const ParsedAttrInfoTestDeclOnly Instance;
+
+// CHECK-LABEL: struct ParsedAttrInfoTestStmtOnly final : public 
StmtOnlyParsedAttrInfo {
+// CHECK: constexpr ParsedAttrInfoTestStmtOnly() : StmtOnlyParsedAttrInfo(
+// CHECK: bool diagAppertainsToStmt(
+// CHECK-NOT: bool diagAppertainsToDecl(
+// CHECK: static const ParsedAttrInfoTestStmtOnly Instance;
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp 
b/clang/utils/TableGen/ClangAttrEmitter.cpp
index 1eaec5f07c75e..8001542b95817 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -4480,6 +4480,54 @@ static void GenerateCustomAppertainsTo(const Record 
&Subject, raw_ostream &OS) {
   CustomSubjectSet.insert(FnName);
 }
 
+static StringRef getParsedAttrInfoBaseClass(const Record &Attr) {
+  if (Attr.isValueUnset("Subjects"))
+    return "ParsedAttrInfo";
+
+  const Record *SubjectObj = Attr.getValueAsDef("Subjects");
+  std::vector<const Record *> Subjects =
+      SubjectObj->getValueAsListOfDefs("Subjects");
+  if (Subjects.empty())
+    return "ParsedAttrInfo";
+
+  bool HasStmtSubject = any_of(
+      Subjects, [](const Record *R) { return R->isSubClassOf("StmtNode"); });
+  bool HasDeclSubject = any_of(
+      Subjects, [](const Record *R) { return !R->isSubClassOf("StmtNode"); });
+  if (HasDeclSubject == HasStmtSubject)
+    return "ParsedAttrInfo";
+  return HasDeclSubject ? "DeclOnlyParsedAttrInfo" : "StmtOnlyParsedAttrInfo";
+}
+
+static void GenerateAppertainsToBaseClasses(raw_ostream &OS) {
+  OS << R"cpp(
+struct DeclOnlyParsedAttrInfo : ParsedAttrInfo {
+  using ParsedAttrInfo::ParsedAttrInfo;
+
+  bool diagAppertainsToStmt(Sema &S, const ParsedAttr &AL,
+                            const Stmt *St) const override {
+    S.Diag(AL.getLoc(), diag::err_decl_attribute_invalid_on_stmt)
+        << AL << AL.isRegularKeywordAttribute() << St->getBeginLoc();
+    return false;
+  }
+};
+static_assert(sizeof(DeclOnlyParsedAttrInfo) == sizeof(ParsedAttrInfo));
+
+struct StmtOnlyParsedAttrInfo : ParsedAttrInfo {
+  using ParsedAttrInfo::ParsedAttrInfo;
+
+  bool diagAppertainsToDecl(Sema &S, const ParsedAttr &AL,
+                            const Decl *D) const override {
+    S.Diag(AL.getLoc(), diag::err_attribute_invalid_on_decl)
+        << AL << AL.isRegularKeywordAttribute() << D->getLocation();
+    return false;
+  }
+};
+static_assert(sizeof(StmtOnlyParsedAttrInfo) == sizeof(ParsedAttrInfo));
+
+)cpp";
+}
+
 static void GenerateAppertainsTo(const Record &Attr, raw_ostream &OS) {
   // If the attribute does not contain a Subjects definition, then use the
   // default appertainsTo logic.
@@ -4512,21 +4560,9 @@ static void GenerateAppertainsTo(const Record &Attr, 
raw_ostream &OS) {
   // FIXME: this assertion will be wrong if we ever add type attribute 
subjects.
   assert(DeclSubjects.size() + StmtSubjects.size() == Subjects.size());
 
-  if (DeclSubjects.empty()) {
-    // If there are no decl subjects but there are stmt subjects, diagnose
-    // trying to apply a statement attribute to a declaration.
-    if (!StmtSubjects.empty()) {
-      OS << "bool diagAppertainsToDecl(Sema &S, const ParsedAttr &AL, ";
-      OS << "const Decl *D) const override {\n";
-      OS << "  S.Diag(AL.getLoc(), diag::err_attribute_invalid_on_decl)\n";
-      OS << "    << AL << AL.isRegularKeywordAttribute() << "
-            "D->getLocation();\n";
-      OS << "  return false;\n";
-      OS << "}\n\n";
-    }
-  } else {
-    // Otherwise, generate an appertainsTo check specific to this attribute
-    // which checks all of the given subjects against the Decl passed in.
+  if (!DeclSubjects.empty()) {
+    // Generate an appertainsTo check specific to this attribute which checks
+    // all of the given declaration subjects against the Decl passed in.
     OS << "bool diagAppertainsToDecl(Sema &S, ";
     OS << "const ParsedAttr &Attr, const Decl *D) const override {\n";
     OS << "  if (";
@@ -4557,19 +4593,7 @@ static void GenerateAppertainsTo(const Record &Attr, 
raw_ostream &OS) {
     OS << "}\n\n";
   }
 
-  if (StmtSubjects.empty()) {
-    // If there are no stmt subjects but there are decl subjects, diagnose
-    // trying to apply a declaration attribute to a statement.
-    if (!DeclSubjects.empty()) {
-      OS << "bool diagAppertainsToStmt(Sema &S, const ParsedAttr &AL, ";
-      OS << "const Stmt *St) const override {\n";
-      OS << "  S.Diag(AL.getLoc(), 
diag::err_decl_attribute_invalid_on_stmt)\n";
-      OS << "    << AL << AL.isRegularKeywordAttribute() << "
-            "St->getBeginLoc();\n";
-      OS << "  return false;\n";
-      OS << "}\n\n";
-    }
-  } else {
+  if (!StmtSubjects.empty()) {
     // Now, do the same for statements.
     OS << "bool diagAppertainsToStmt(Sema &S, ";
     OS << "const ParsedAttr &Attr, const Stmt *St) const override {\n";
@@ -4984,6 +5008,8 @@ void EmitClangAttrParsedAttrImpl(const RecordKeeper 
&Records, raw_ostream &OS) {
         GenerateCustomAppertainsTo(*Subject, OS);
   }
 
+  GenerateAppertainsToBaseClasses(OS);
+
   // This stream is used to collect all of the declaration attribute merging
   // logic for performing mutual exclusion checks. This gets emitted at the
   // end of the file in a helper function of its own.
@@ -5002,6 +5028,7 @@ void EmitClangAttrParsedAttrImpl(const RecordKeeper 
&Records, raw_ostream &OS) {
     // ParsedAttr.cpp.
     const std::string &AttrName = I->first;
     const Record &Attr = *I->second;
+    StringRef BaseClass = getParsedAttrInfoBaseClass(Attr);
     auto Spellings = GetFlattenedSpellings(Attr);
     if (!Spellings.empty()) {
       OS << "static constexpr ParsedAttrInfo::Spelling " << I->first
@@ -5041,9 +5068,10 @@ void EmitClangAttrParsedAttrImpl(const RecordKeeper 
&Records, raw_ostream &OS) {
       OS << "};\n";
     }
 
-    OS << "struct ParsedAttrInfo" << I->first
-       << " final : public ParsedAttrInfo {\n";
-    OS << "  constexpr ParsedAttrInfo" << I->first << "() : ParsedAttrInfo(\n";
+    OS << "struct ParsedAttrInfo" << I->first << " final : public " << 
BaseClass
+       << " {\n";
+    OS << "  constexpr ParsedAttrInfo" << I->first << "() : " << BaseClass
+       << "(\n";
     OS << "    /*AttrKind=*/ParsedAttr::AT_" << AttrName << ",\n";
     emitArgInfo(Attr, OS);
     OS << "    /*HasCustomParsing=*/";

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to