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
