[clang] [clang][Sema] Share ParsedAttrInfo cross-domain diagnostics (PR #202654)
David Zbarsky via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 9 07:01:33 PDT 2026
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 <dzbarsky at gmail.com>
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 at +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=*/";
More information about the cfe-commits
mailing list