https://github.com/ahatanak updated 
https://github.com/llvm/llvm-project/pull/168769

>From 516b90f080fe28c93e9127686773d3f72c96517b Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <[email protected]>
Date: Wed, 19 Nov 2025 12:09:26 -0800
Subject: [PATCH 1/5] [AST] Support structural equivalence checking of
 attributes on Decls

The structural equivalence checker currently treats any explicit
attributes on a declaration as a reason to consider the declarations
non-equivalent in C23 mode, even when both declarations carry the same
attributes. This is unnecessarily strict and causes two otherwise
equivalent declarations to be rejected just because they carry
explicitly annotated attributes.

This patch enables structural equivalence checking to accept selected
attributes as long as the attributes on both definitions are equivalent
and appear in the same order. The initial implementation adds support
for three attributes: Availability, EnumExtensibility, and Unused.

Additional attribute kinds can be added incrementally. This design also
allows these utilities to be generated automatically by tablegen in
the future.

Inherited attributes that are supported are ignored when determining
structural equivalence, because inherited attributes should not affect
whether two definitions are structurally compatible.

This patch also moves the call to CheckStructurallyEquivalentAttributes
so that attribute comparison is performed between two definitions of
record decls rather than between a declaration and a definition.

rdar://163304242
---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 146 ++++++++++++++++++---
 clang/test/C/C23/n3037.c                   |  87 ++++++++++++
 2 files changed, 217 insertions(+), 16 deletions(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp 
b/clang/lib/AST/ASTStructuralEquivalence.cpp
index da64c92221837..fc7f44c6ba563 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -93,6 +93,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include <cassert>
 #include <optional>
+#include <set>
 #include <utility>
 
 using namespace clang;
@@ -451,6 +452,123 @@ class StmtComparer {
 };
 } // namespace
 
+namespace {
+enum class AttrComparisonKind { Equal, NotEqual };
+
+/// Represents the result of comparing the attribute sets on two decls. If the
+/// sets are incompatible, A1/A2 point to the offending attributes.
+struct AttrComparisonResult {
+  AttrComparisonKind Kind = AttrComparisonKind::Equal;
+  const Attr *A1 = nullptr, *A2 = nullptr;
+};
+} // namespace
+
+static AttrComparisonResult
+areAvailabilityAttrsEqual(const AvailabilityAttr *A1,
+                          const AvailabilityAttr *A2) {
+  if (A1->getPlatform() == A2->getPlatform() &&
+      A1->getIntroduced() == A2->getIntroduced() &&
+      A1->getDeprecated() == A2->getDeprecated() &&
+      A1->getObsoleted() == A2->getObsoleted() &&
+      A1->getUnavailable() == A2->getUnavailable() &&
+      A1->getMessage() == A2->getMessage() &&
+      A1->getReplacement() == A2->getReplacement() &&
+      A1->getStrict() == A2->getStrict() &&
+      A1->getPriority() == A2->getPriority() &&
+      A1->getEnvironment() == A2->getEnvironment())
+    return {AttrComparisonKind::Equal};
+  return {AttrComparisonKind::NotEqual, A1, A2};
+}
+
+static AttrComparisonResult
+areEnumExtensibilityAttrsEqual(const EnumExtensibilityAttr *A1,
+                               const EnumExtensibilityAttr *A2) {
+  if (A1->getExtensibility() == A2->getExtensibility())
+    return {AttrComparisonKind::Equal};
+  return {AttrComparisonKind::NotEqual, A1, A2};
+}
+
+static AttrComparisonResult areAttrsEqual(const Attr *A1, const Attr *A2) {
+  auto Kind1 = A1->getKind(), Kind2 = A2->getKind();
+  if (Kind1 != Kind2)
+    return {AttrComparisonKind::NotEqual, A1, A2};
+
+  switch (Kind1) {
+  case attr::Availability:
+    return areAvailabilityAttrsEqual(cast<AvailabilityAttr>(A1),
+                                     cast<AvailabilityAttr>(A2));
+  case attr::EnumExtensibility:
+    return areEnumExtensibilityAttrsEqual(cast<EnumExtensibilityAttr>(A1),
+                                          cast<EnumExtensibilityAttr>(A2));
+  case attr::Unused:
+    return {AttrComparisonKind::Equal};
+  default:
+    llvm_unreachable("unexpected attr kind");
+  }
+}
+
+static bool compareAttrKind(const Attr *A1, const Attr *A2) {
+  return A1->getKind() < A2->getKind();
+}
+
+namespace {
+using AttrSet = std::multiset<const Attr *, decltype(&compareAttrKind)>;
+}
+
+/// Collects all supported, non-inherited attributes from the given decl.
+/// Returns true on success. If the decl contains any unsupported attribute,
+/// returns false and sets UnsupportedAttr to point to that attribute.
+static bool collectComparableAttrs(const Decl *D, AttrSet &Attrs,
+                                   const Attr *&UnsupportedAttr) {
+  for (const Attr *A : D->attrs()) {
+    switch (A->getKind()) {
+    case attr::Availability:
+    case attr::EnumExtensibility:
+    case attr::Unused:
+      if (!A->isInherited())
+        Attrs.insert(A);
+      break;
+
+    default:
+      UnsupportedAttr = A;
+      return false; // unsupported attribute
+    }
+  }
+
+  return true;
+}
+
+/// Determines whether D1 and D2 have compatible sets of attributes for the
+/// purposes of structural equivalence checking.
+static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1,
+                                                   const Decl *D2) {
+  if (D1->isImplicit() || D2->isImplicit())
+    return {AttrComparisonKind::Equal};
+
+  AttrSet A1(&compareAttrKind), A2(&compareAttrKind);
+
+  const Attr *UnsupportedAttr1 = nullptr, *UnsupportedAttr2 = nullptr;
+  bool HasUnsupportedAttr1 = collectComparableAttrs(D1, A1, UnsupportedAttr1);
+  bool HasUnsupportedAttr2 = collectComparableAttrs(D2, A2, UnsupportedAttr2);
+
+  if (!HasUnsupportedAttr1 || !HasUnsupportedAttr2)
+    return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2};
+
+  auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end();
+  for (; I1 != E1 && I2 != E2; ++I1, ++I2) {
+    AttrComparisonResult R = areAttrsEqual(*I1, *I2);
+    if (R.Kind != AttrComparisonKind::Equal)
+      return R;
+  }
+
+  if (I1 != E1)
+    return {AttrComparisonKind::NotEqual, *I1};
+  if (I2 != E2)
+    return {AttrComparisonKind::NotEqual, nullptr, *I2};
+
+  return {AttrComparisonKind::Equal};
+}
+
 static bool
 CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
                                       const Decl *D1, const Decl *D2,
@@ -465,21 +583,17 @@ 
CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
   // the same semantic attribute, differences in attribute arguments, order
   // in which attributes are applied, how to merge attributes if the types are
   // structurally equivalent, etc.
-  const Attr *D1Attr = nullptr, *D2Attr = nullptr;
-  if (D1->hasAttrs())
-    D1Attr = *D1->getAttrs().begin();
-  if (D2->hasAttrs())
-    D2Attr = *D2->getAttrs().begin();
-  if ((D1Attr || D2Attr) && !D1->isImplicit() && !D2->isImplicit()) {
+  AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2);
+  if (R.Kind != AttrComparisonKind::Equal) {
     const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
     Context.Diag2(DiagnoseDecl->getLocation(),
                   diag::warn_odr_tag_type_with_attributes)
         << Context.ToCtx.getTypeDeclType(DiagnoseDecl)
         << (PrimaryDecl != nullptr);
-    if (D1Attr)
-      Context.Diag1(D1Attr->getLoc(), diag::note_odr_attr_here) << D1Attr;
-    if (D2Attr)
-      Context.Diag1(D2Attr->getLoc(), diag::note_odr_attr_here) << D2Attr;
+    if (R.A1)
+      Context.Diag1(R.A1->getLoc(), diag::note_odr_attr_here) << R.A1;
+    if (R.A2)
+      Context.Diag1(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2;
   }
 
   // The above diagnostic is a warning which defaults to an error. If treated
@@ -1791,12 +1905,6 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
     }
   }
 
-  // In C23 mode, check for structural equivalence of attributes on the record
-  // itself. FIXME: Should this happen in C++ as well?
-  if (Context.LangOpts.C23 &&
-      !CheckStructurallyEquivalentAttributes(Context, D1, D2))
-    return false;
-
   // If the records occur in different context (namespace), these should be
   // different. This is specially important if the definition of one or both
   // records is missing. In C23, different contexts do not make for a different
@@ -1838,6 +1946,12 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
   if (!D1 || !D2)
     return !Context.LangOpts.C23;
 
+  // In C23 mode, check for structural equivalence of attributes on the record
+  // itself. FIXME: Should this happen in C++ as well?
+  if (Context.LangOpts.C23 &&
+      !CheckStructurallyEquivalentAttributes(Context, D1, D2))
+    return false;
+
   // If any of the records has external storage and we do a minimal check (or
   // AST import) we assume they are equivalent. (If we didn't have this
   // assumption then `RecordDecl::LoadFieldsFromExternalStorage` could trigger
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 113ecf74d8bef..0a82e8846d92a 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -515,3 +515,90 @@ void gh149965(void) {
   enum E2 *eptr2;
   [[maybe_unused]] __typeof__(x2.h) *ptr2 = eptr2;
 }
+
+struct __attribute__((availability(ios, introduced = 14), availability(macos, 
introduced = 12))) AvailS0 {
+  // c17-note@-1 4 {{previous definition is here}}
+  // c23-note@-2 2 {{attribute 'availability' here}}
+  int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16)));
+  // c23-note@-1 {{attribute 'availability' here}}
+};
+
+struct __attribute__((availability(ios, introduced = 14), availability(macos, 
introduced = 12))) AvailS0 {
+  // c17-error@-1 {{redefinition of 'AvailS0'}}
+  int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16)));
+};
+
+// The order of the attributes matters.
+struct __attribute__((availability(macos, introduced = 12), availability(ios, 
introduced = 14))) AvailS0 {
+  // c17-error@-1 {{redefinition of 'AvailS0'}}
+  // c23-error@-2 {{type 'struct AvailS0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'availability' here}}
+  int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16)));
+};
+
+struct __attribute__((availability(ios, introduced = 14))) 
[[__maybe_unused__]] AvailS0 {
+  // c17-error@-1 {{redefinition of 'AvailS0'}}
+  // c23-error@-2 {{type 'struct AvailS0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'maybe_unused' here}}
+  int f0 __attribute__((availability(ios, introduced = 15, deprecated = 16)));
+};
+
+struct __attribute__((availability(ios, introduced = 14), availability(macos, 
introduced = 12))) AvailS0 {
+  // c17-error@-1 {{redefinition of 'AvailS0'}}
+  // c23-error@-2 {{type 'struct AvailS0' has a member with an attribute which 
currently causes the types to be treated as though they are incompatible}}
+  int f0 __attribute__((availability(macos, introduced = 13)));
+  // c23-note@-1 {{attribute 'availability' here}}
+};
+
+enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) 
AvailE0 {
+  // c17-note@-1 {{previous definition is here}}
+  // c23-note@-2 {{attribute 'warn_unused_result' here}}
+  A_E0
+  // c17-note@-1 {{previous definition is here}}
+};
+
+enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) 
AvailE0 {
+  // c17-error@-1 {{redefinition of 'AvailE0'}}
+  // c23-error@-2 {{type 'enum AvailE0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'warn_unused_result' here}}
+  A_E0
+  // c17-error@-1 {{redefinition of enumerator 'A_E0'}}
+};
+
+enum __attribute__((enum_extensibility(closed))) AvailE1 {
+  // c17-note@-1 {{previous definition is here}}
+  A_E1
+  // c17-note@-1 {{previous definition is here}}
+};
+
+enum __attribute__((enum_extensibility(closed))) AvailE1 {
+  // c17-error@-1 {{redefinition of 'AvailE1'}}
+  A_E1
+  // c17-error@-1 {{redefinition of enumerator 'A_E1'}}
+};
+
+struct [[__maybe_unused__]] AvailS1;
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS1 {
+  // c17-note@-1 {{previous definition is here}}
+  int a;
+};
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS1 {
+  // c17-error@-1 {{redefinition of 'AvailS1'}}
+  int a;
+};
+
+struct __attribute__((warn_unused_result)) AvailS2;
+// c23-note@-1 {{attribute 'warn_unused_result' here}}
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS2 {
+  // c17-note@-1 {{previous definition is here}}
+  int a;
+};
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS2 {
+  // c17-error@-1 {{redefinition of 'AvailS2'}}
+  // c23-error@-2 {{type 'struct AvailS2' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  int a;
+};

>From 669be8d167b465e9ed2980e687068251a4d69024 Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <[email protected]>
Date: Wed, 19 Nov 2025 16:06:47 -0800
Subject: [PATCH 2/5] Simplify function and rename confusing variables

---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp 
b/clang/lib/AST/ASTStructuralEquivalence.cpp
index fc7f44c6ba563..b2e7cea3a8180 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -516,10 +516,9 @@ using AttrSet = std::multiset<const Attr *, 
decltype(&compareAttrKind)>;
 }
 
 /// Collects all supported, non-inherited attributes from the given decl.
-/// Returns true on success. If the decl contains any unsupported attribute,
-/// returns false and sets UnsupportedAttr to point to that attribute.
-static bool collectComparableAttrs(const Decl *D, AttrSet &Attrs,
-                                   const Attr *&UnsupportedAttr) {
+/// If the decl doesn't contain any unsupported attributes, returns a nullptr,
+/// otherwise returns the first unsupported attribute.
+static const Attr *collectComparableAttrs(const Decl *D, AttrSet &Attrs) {
   for (const Attr *A : D->attrs()) {
     switch (A->getKind()) {
     case attr::Availability:
@@ -528,14 +527,12 @@ static bool collectComparableAttrs(const Decl *D, AttrSet 
&Attrs,
       if (!A->isInherited())
         Attrs.insert(A);
       break;
-
     default:
-      UnsupportedAttr = A;
-      return false; // unsupported attribute
+      return A; // unsupported attribute
     }
   }
 
-  return true;
+  return nullptr;
 }
 
 /// Determines whether D1 and D2 have compatible sets of attributes for the
@@ -547,11 +544,10 @@ static AttrComparisonResult areDeclAttrsEquivalent(const 
Decl *D1,
 
   AttrSet A1(&compareAttrKind), A2(&compareAttrKind);
 
-  const Attr *UnsupportedAttr1 = nullptr, *UnsupportedAttr2 = nullptr;
-  bool HasUnsupportedAttr1 = collectComparableAttrs(D1, A1, UnsupportedAttr1);
-  bool HasUnsupportedAttr2 = collectComparableAttrs(D2, A2, UnsupportedAttr2);
+  const Attr *UnsupportedAttr1 = collectComparableAttrs(D1, A1);
+  const Attr *UnsupportedAttr2 = collectComparableAttrs(D2, A2);
 
-  if (!HasUnsupportedAttr1 || !HasUnsupportedAttr2)
+  if (UnsupportedAttr1 || UnsupportedAttr2)
     return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2};
 
   auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end();
@@ -563,7 +559,7 @@ static AttrComparisonResult areDeclAttrsEquivalent(const 
Decl *D1,
 
   if (I1 != E1)
     return {AttrComparisonKind::NotEqual, *I1};
-  if (I2 != E2)
+  else if (I2 != E2)
     return {AttrComparisonKind::NotEqual, nullptr, *I2};
 
   return {AttrComparisonKind::Equal};

>From b9e91d3c4fe42f523d845ff9d5527aa2374f4ca7 Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <[email protected]>
Date: Wed, 19 Nov 2025 19:31:03 -0800
Subject: [PATCH 3/5] =?UTF-8?q?Don=E2=80=99t=20use=20else=20after=20a=20re?=
 =?UTF-8?q?turn?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp 
b/clang/lib/AST/ASTStructuralEquivalence.cpp
index b2e7cea3a8180..19dfb38a3dab6 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -559,7 +559,7 @@ static AttrComparisonResult areDeclAttrsEquivalent(const 
Decl *D1,
 
   if (I1 != E1)
     return {AttrComparisonKind::NotEqual, *I1};
-  else if (I2 != E2)
+  if (I2 != E2)
     return {AttrComparisonKind::NotEqual, nullptr, *I2};
 
   return {AttrComparisonKind::Equal};

>From 74efd1b4ed45c09903154c5386a95ec9e7ec50ea Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <[email protected]>
Date: Mon, 22 Dec 2025 06:34:22 -0800
Subject: [PATCH 4/5] Automatically generate functions for attribute
 equivalence checking

---
 .../clang/AST/ASTStructuralEquivalence.h      |  49 ++-
 clang/include/clang/AST/Attr.h                |   4 +
 clang/include/clang/Basic/Attr.td             |   4 +-
 clang/include/clang/Sema/Sema.h               |  22 +-
 clang/lib/AST/ASTStructuralEquivalence.cpp    | 178 ++++-----
 clang/lib/AST/AttrImpl.cpp                    |  82 ++++
 clang/lib/AST/DeclBase.cpp                    |   2 +-
 clang/lib/Index/CommentToXML.cpp              |   2 +-
 clang/lib/Sema/SemaAvailability.cpp           |   2 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |  18 +-
 clang/lib/Sema/SemaExprObjC.cpp               |  10 +-
 clang/lib/Sema/SemaHLSL.cpp                   |   4 +-
 clang/lib/Sema/SemaObjC.cpp                   |   2 +-
 clang/test/C/C23/n3037.c                      | 358 ++++++++++++++++--
 clang/utils/TableGen/ClangAttrEmitter.cpp     |  89 ++++-
 15 files changed, 652 insertions(+), 174 deletions(-)

diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h 
b/clang/include/clang/AST/ASTStructuralEquivalence.h
index 5e431a14f1756..8fa21f8a223a5 100644
--- a/clang/include/clang/AST/ASTStructuralEquivalence.h
+++ b/clang/include/clang/AST/ASTStructuralEquivalence.h
@@ -61,6 +61,37 @@ struct StructuralEquivalenceContext {
   /// (which we have already complained about).
   NonEquivalentDeclSet &NonEquivalentDecls;
 
+  /// RAII helper used during attribute equivalence checking. While this object
+  /// is alive, attribute comparison uses a local queue and visited set in 
order
+  /// not to pollute the ones stored in StructuralEquivalenceContext (i.e.,
+  /// DeclsToCheck and VisitedDecls).
+  struct AttrScopedAttrEquivalenceContext {
+    AttrScopedAttrEquivalenceContext(StructuralEquivalenceContext &Ctx)
+        : Ctx(Ctx), OldDeclsToCheck(Ctx.CurDeclsToCheck),
+          OldVisitedDecls(Ctx.CurVisitedDecls), OldComplain(Ctx.Complain) {
+      Ctx.CurDeclsToCheck = &LocalDeclsToCheck;
+      Ctx.CurVisitedDecls = &LocalVisitedDecls;
+      // Silence diagnostics while trying to determine whether the attributes
+      // are equivalent.
+      Ctx.Complain = false;
+    }
+    ~AttrScopedAttrEquivalenceContext();
+    StructuralEquivalenceContext &Ctx;
+    std::queue<std::pair<Decl *, Decl *>> LocalDeclsToCheck, *OldDeclsToCheck;
+    llvm::DenseSet<std::pair<Decl *, Decl *>> LocalVisitedDecls,
+        *OldVisitedDecls;
+    bool OldComplain;
+  };
+
+  /// Pointers to the current decl queue and visited decl set that are being
+  /// used.
+  std::queue<std::pair<Decl *, Decl *>> *CurDeclsToCheck;
+  llvm::DenseSet<std::pair<Decl *, Decl *>> *CurVisitedDecls;
+
+  bool isInAttrEquivalenceCheck() const {
+    return CurVisitedDecls != &VisitedDecls;
+  }
+
   StructuralEquivalenceKind EqKind;
 
   /// Whether we're being strict about the spelling of types when
@@ -88,7 +119,8 @@ struct StructuralEquivalenceContext {
                                bool ErrorOnTagTypeMismatch = false,
                                bool IgnoreTemplateParmDepth = false)
       : LangOpts(LangOpts), FromCtx(FromCtx), ToCtx(ToCtx),
-        NonEquivalentDecls(NonEquivalentDecls), EqKind(EqKind),
+        NonEquivalentDecls(NonEquivalentDecls), CurDeclsToCheck(&DeclsToCheck),
+        CurVisitedDecls(&VisitedDecls), EqKind(EqKind),
         StrictTypeSpelling(StrictTypeSpelling),
         ErrorOnTagTypeMismatch(ErrorOnTagTypeMismatch), Complain(Complain),
         IgnoreTemplateParmDepth(IgnoreTemplateParmDepth) {}
@@ -134,7 +166,12 @@ struct StructuralEquivalenceContext {
   // relevant warning for the input error diagnostic.
   unsigned getApplicableDiagnostic(unsigned ErrorDiagnostic);
 
+  /// Iterate over the decl pairs in CurDeclsToCheck until either an
+  /// inequivalent pair is found or the queue is empty.
+  bool checkDeclQueue();
+
 private:
+
   /// Finish checking all of the structural equivalences.
   ///
   /// \returns true if the equivalence check failed (non-equivalence detected),
@@ -152,6 +189,16 @@ struct StructuralEquivalenceContext {
   bool CheckKindSpecificEquivalence(Decl *D1, Decl *D2);
 };
 
+/// Expose these functions so that they can be called by the functions that
+/// check equivalence of attribute arguments.
+namespace ASTStructuralEquivalence {
+bool isEquivalent(StructuralEquivalenceContext &Context, QualType T1,
+                  QualType T2);
+bool isEquivalent(StructuralEquivalenceContext &Context, const Stmt *S1,
+                  const Stmt *S2);
+bool isEquivalent(const IdentifierInfo *Name1, const IdentifierInfo *Name2);
+}
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_ASTSTRUCTURALEQUIVALENCE_H
diff --git a/clang/include/clang/AST/Attr.h b/clang/include/clang/AST/Attr.h
index e36184f232f8a..6c38437e88a44 100644
--- a/clang/include/clang/AST/Attr.h
+++ b/clang/include/clang/AST/Attr.h
@@ -40,6 +40,7 @@ class AttributeCommonInfo;
 class FunctionDecl;
 class OMPTraitInfo;
 class OpenACCClause;
+struct StructuralEquivalenceContext;
 
 /// Attr - This represents one attribute.
 class Attr : public AttributeCommonInfo {
@@ -112,6 +113,9 @@ class Attr : public AttributeCommonInfo {
 
   bool isLateParsed() const { return IsLateParsed; }
 
+  bool isEquivalent(const Attr &Other,
+                    StructuralEquivalenceContext &Context) const;
+
   // Pretty print this attribute.
   void printPretty(raw_ostream &OS, const PrintingPolicy &Policy) const;
 
diff --git a/clang/include/clang/Basic/Attr.td 
b/clang/include/clang/Basic/Attr.td
index 8e5f7ef0bb82d..c02938b6295ce 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -762,6 +762,7 @@ class Attr {
   // Any documentation that should be associated with the attribute. Since an
   // attribute may be documented under multiple categories, more than one
   // Documentation entry may be listed.
+  string comparisonFn = "";
   list<Documentation> Documentation;
 }
 
@@ -886,6 +887,7 @@ def Aligned : InheritableAttr {
                    Accessor<"isAlignas", [CustomKeyword<"alignas">,
                                           CustomKeyword<"_Alignas">]>,
                    Accessor<"isDeclspec",[Declspec<"align">]>];
+  let comparisonFn = "areAlignedAttrsEqual";
   let Documentation = [Undocumented];
 }
 
@@ -1486,7 +1488,7 @@ def CPUSpecific : InheritableAttr {
   let Subjects = SubjectList<[Function]>;
   let Documentation = [CPUSpecificCPUDispatchDocs];
   let AdditionalMembers = [{
-    IdentifierInfo *getCPUName(unsigned Index) const {
+    const IdentifierInfo *getCPUName(unsigned Index) const {
       return *(cpus_begin() + Index);
     }
   }];
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index cbfcc9bc0ea99..3551ca5999f6c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4914,12 +4914,14 @@ class Sema final : public SemaBase {
   bool CheckAttrTarget(const ParsedAttr &CurrAttr);
   bool CheckAttrNoArgs(const ParsedAttr &CurrAttr);
 
-  AvailabilityAttr *mergeAvailabilityAttr(
-      NamedDecl *D, const AttributeCommonInfo &CI, IdentifierInfo *Platform,
-      bool Implicit, VersionTuple Introduced, VersionTuple Deprecated,
-      VersionTuple Obsoleted, bool IsUnavailable, StringRef Message,
-      bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK,
-      int Priority, IdentifierInfo *IIEnvironment);
+  AvailabilityAttr *
+  mergeAvailabilityAttr(NamedDecl *D, const AttributeCommonInfo &CI,
+                        const IdentifierInfo *Platform, bool Implicit,
+                        VersionTuple Introduced, VersionTuple Deprecated,
+                        VersionTuple Obsoleted, bool IsUnavailable,
+                        StringRef Message, bool IsStrict, StringRef 
Replacement,
+                        AvailabilityMergeKind AMK, int Priority,
+                        const IdentifierInfo *IIEnvironment);
 
   TypeVisibilityAttr *
   mergeTypeVisibilityAttr(Decl *D, const AttributeCommonInfo &CI,
@@ -4950,11 +4952,11 @@ class Sema final : public SemaBase {
   ErrorAttr *mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI,
                             StringRef NewUserDiagnostic);
   FormatAttr *mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
-                              IdentifierInfo *Format, int FormatIdx,
+                              const IdentifierInfo *Format, int FormatIdx,
                               int FirstArg);
   FormatMatchesAttr *mergeFormatMatchesAttr(Decl *D,
                                             const AttributeCommonInfo &CI,
-                                            IdentifierInfo *Format,
+                                            const IdentifierInfo *Format,
                                             int FormatIdx,
                                             StringLiteral *FormatStr);
 
@@ -4980,8 +4982,8 @@ class Sema final : public SemaBase {
   void CheckAlignasUnderalignment(Decl *D);
 
   /// AddModeAttr - Adds a mode attribute to a particular declaration.
-  void AddModeAttr(Decl *D, const AttributeCommonInfo &CI, IdentifierInfo 
*Name,
-                   bool InInstantiation = false);
+  void AddModeAttr(Decl *D, const AttributeCommonInfo &CI,
+                   const IdentifierInfo *Name, bool InInstantiation = false);
   AlwaysInlineAttr *mergeAlwaysInlineAttr(Decl *D,
                                           const AttributeCommonInfo &CI,
                                           const IdentifierInfo *Ident);
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp 
b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 19dfb38a3dab6..d9a6d14eb02b3 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -88,12 +88,12 @@
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/APInt.h"
 #include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
 #include <cassert>
 #include <optional>
-#include <set>
 #include <utility>
 
 using namespace clang;
@@ -453,116 +453,58 @@ class StmtComparer {
 } // namespace
 
 namespace {
-enum class AttrComparisonKind { Equal, NotEqual };
-
 /// Represents the result of comparing the attribute sets on two decls. If the
 /// sets are incompatible, A1/A2 point to the offending attributes.
 struct AttrComparisonResult {
-  AttrComparisonKind Kind = AttrComparisonKind::Equal;
+  bool Kind = false;
   const Attr *A1 = nullptr, *A2 = nullptr;
 };
 } // namespace
 
-static AttrComparisonResult
-areAvailabilityAttrsEqual(const AvailabilityAttr *A1,
-                          const AvailabilityAttr *A2) {
-  if (A1->getPlatform() == A2->getPlatform() &&
-      A1->getIntroduced() == A2->getIntroduced() &&
-      A1->getDeprecated() == A2->getDeprecated() &&
-      A1->getObsoleted() == A2->getObsoleted() &&
-      A1->getUnavailable() == A2->getUnavailable() &&
-      A1->getMessage() == A2->getMessage() &&
-      A1->getReplacement() == A2->getReplacement() &&
-      A1->getStrict() == A2->getStrict() &&
-      A1->getPriority() == A2->getPriority() &&
-      A1->getEnvironment() == A2->getEnvironment())
-    return {AttrComparisonKind::Equal};
-  return {AttrComparisonKind::NotEqual, A1, A2};
-}
-
-static AttrComparisonResult
-areEnumExtensibilityAttrsEqual(const EnumExtensibilityAttr *A1,
-                               const EnumExtensibilityAttr *A2) {
-  if (A1->getExtensibility() == A2->getExtensibility())
-    return {AttrComparisonKind::Equal};
-  return {AttrComparisonKind::NotEqual, A1, A2};
-}
-
-static AttrComparisonResult areAttrsEqual(const Attr *A1, const Attr *A2) {
-  auto Kind1 = A1->getKind(), Kind2 = A2->getKind();
-  if (Kind1 != Kind2)
-    return {AttrComparisonKind::NotEqual, A1, A2};
-
-  switch (Kind1) {
-  case attr::Availability:
-    return areAvailabilityAttrsEqual(cast<AvailabilityAttr>(A1),
-                                     cast<AvailabilityAttr>(A2));
-  case attr::EnumExtensibility:
-    return areEnumExtensibilityAttrsEqual(cast<EnumExtensibilityAttr>(A1),
-                                          cast<EnumExtensibilityAttr>(A2));
-  case attr::Unused:
-    return {AttrComparisonKind::Equal};
-  default:
-    llvm_unreachable("unexpected attr kind");
-  }
-}
-
-static bool compareAttrKind(const Attr *A1, const Attr *A2) {
-  return A1->getKind() < A2->getKind();
-}
-
 namespace {
-using AttrSet = std::multiset<const Attr *, decltype(&compareAttrKind)>;
+using AttrSet = llvm::SmallVector<const Attr *, 2>;
 }
 
-/// Collects all supported, non-inherited attributes from the given decl.
-/// If the decl doesn't contain any unsupported attributes, returns a nullptr,
-/// otherwise returns the first unsupported attribute.
-static const Attr *collectComparableAttrs(const Decl *D, AttrSet &Attrs) {
-  for (const Attr *A : D->attrs()) {
-    switch (A->getKind()) {
-    case attr::Availability:
-    case attr::EnumExtensibility:
-    case attr::Unused:
-      if (!A->isInherited())
-        Attrs.insert(A);
-      break;
-    default:
-      return A; // unsupported attribute
-    }
-  }
-
-  return nullptr;
+StructuralEquivalenceContext::AttrScopedAttrEquivalenceContext::
+    ~AttrScopedAttrEquivalenceContext() {
+  Ctx.CurDeclsToCheck = OldDeclsToCheck;
+  Ctx.CurVisitedDecls = OldVisitedDecls;
+  Ctx.Complain = OldComplain;
 }
 
 /// Determines whether D1 and D2 have compatible sets of attributes for the
 /// purposes of structural equivalence checking.
-static AttrComparisonResult areDeclAttrsEquivalent(const Decl *D1,
-                                                   const Decl *D2) {
+static AttrComparisonResult
+areDeclAttrsEquivalent(const Decl *D1, const Decl *D2,
+                       StructuralEquivalenceContext &Context) {
   if (D1->isImplicit() || D2->isImplicit())
-    return {AttrComparisonKind::Equal};
+    return {true};
 
-  AttrSet A1(&compareAttrKind), A2(&compareAttrKind);
+  AttrSet A1, A2;
 
-  const Attr *UnsupportedAttr1 = collectComparableAttrs(D1, A1);
-  const Attr *UnsupportedAttr2 = collectComparableAttrs(D2, A2);
+  // Ignore inherited attributes.
+  auto RemoveInherited = [](const Attr *A) { return !A->isInherited(); };
 
-  if (UnsupportedAttr1 || UnsupportedAttr2)
-    return {AttrComparisonKind::NotEqual, UnsupportedAttr1, UnsupportedAttr2};
+  llvm::copy_if(D1->attrs(), std::back_inserter(A1), RemoveInherited);
+  llvm::copy_if(D2->attrs(), std::back_inserter(A2), RemoveInherited);
 
   auto I1 = A1.begin(), E1 = A1.end(), I2 = A2.begin(), E2 = A2.end();
   for (; I1 != E1 && I2 != E2; ++I1, ++I2) {
-    AttrComparisonResult R = areAttrsEqual(*I1, *I2);
-    if (R.Kind != AttrComparisonKind::Equal)
-      return R;
+    StructuralEquivalenceContext::AttrScopedAttrEquivalenceContext AttrCtx(
+        Context);
+    bool R = (*I1)->isEquivalent(**I2, Context);
+    if (R)
+      R = !Context.checkDeclQueue();
+    if (!R)
+      return {false, *I1, *I2};
   }
 
   if (I1 != E1)
-    return {AttrComparisonKind::NotEqual, *I1};
+    return {false, *I1};
   if (I2 != E2)
-    return {AttrComparisonKind::NotEqual, nullptr, *I2};
+    return {false, nullptr, *I2};
 
-  return {AttrComparisonKind::Equal};
+  return {true};
 }
 
 static bool
@@ -579,8 +521,8 @@ 
CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
   // the same semantic attribute, differences in attribute arguments, order
   // in which attributes are applied, how to merge attributes if the types are
   // structurally equivalent, etc.
-  AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2);
-  if (R.Kind != AttrComparisonKind::Equal) {
+  AttrComparisonResult R = areDeclAttrsEquivalent(D1, D2, Context);
+  if (!R.Kind) {
     const auto *DiagnoseDecl = cast<TypeDecl>(PrimaryDecl ? PrimaryDecl : D2);
     Context.Diag2(DiagnoseDecl->getLocation(),
                   diag::warn_odr_tag_type_with_attributes)
@@ -589,7 +531,7 @@ 
CheckStructurallyEquivalentAttributes(StructuralEquivalenceContext &Context,
     if (R.A1)
       Context.Diag1(R.A1->getLoc(), diag::note_odr_attr_here) << R.A1;
     if (R.A2)
-      Context.Diag1(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2;
+      Context.Diag2(R.A2->getLoc(), diag::note_odr_attr_here) << R.A2;
   }
 
   // The above diagnostic is a warning which defaults to an error. If treated
@@ -633,8 +575,8 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
 }
 
 /// Determine structural equivalence of two statements.
-static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
-                                     const Stmt *S1, const Stmt *S2) {
+bool ASTStructuralEquivalence::isEquivalent(
+    StructuralEquivalenceContext &Context, const Stmt *S1, const Stmt *S2) {
   if (!S1 || !S2)
     return S1 == S2;
 
@@ -678,15 +620,25 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
   return true;
 }
 
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+                                     const Stmt *S1, const Stmt *S2) {
+  return ASTStructuralEquivalence::isEquivalent(Context, S1, S2);
+}
+
 /// Determine whether two identifiers are equivalent.
-static bool IsStructurallyEquivalent(const IdentifierInfo *Name1,
-                                     const IdentifierInfo *Name2) {
+bool ASTStructuralEquivalence::isEquivalent(const IdentifierInfo *Name1,
+                                            const IdentifierInfo *Name2) {
   if (!Name1 || !Name2)
     return Name1 == Name2;
 
   return Name1->getName() == Name2->getName();
 }
 
+static bool IsStructurallyEquivalent(const IdentifierInfo *Name1,
+                                     const IdentifierInfo *Name2) {
+  return ASTStructuralEquivalence::isEquivalent(Name1, Name2);
+}
+
 /// Determine whether two nested-name-specifiers are equivalent.
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                      NestedNameSpecifier NNS1,
@@ -946,8 +898,8 @@ static bool 
IsEquivalentExceptionSpec(StructuralEquivalenceContext &Context,
 }
 
 /// Determine structural equivalence of two types.
-static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
-                                     QualType T1, QualType T2) {
+bool ASTStructuralEquivalence::isEquivalent(
+    StructuralEquivalenceContext &Context, QualType T1, QualType T2) {
   if (T1.isNull() || T2.isNull())
     return T1.isNull() && T2.isNull();
 
@@ -1602,6 +1554,11 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
   return true;
 }
 
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+                                     QualType T1, QualType T2) {
+  return ASTStructuralEquivalence::isEquivalent(Context, T1, T2);
+}
+
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                      VarDecl *D1, VarDecl *D2) {
   IdentifierInfo *Name1 = D1->getIdentifier();
@@ -1722,6 +1679,14 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                   Context.ToCtx.getCanonicalTagType(Owner2));
 }
 
+/// Determine structural equivalence of two IndirectFields.
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+                                     IndirectFieldDecl *ID1,
+                                     IndirectFieldDecl *ID2) {
+  return IsStructurallyEquivalent(Context, ID1->getAnonField(),
+                                  ID2->getAnonField());
+}
+
 /// Determine structural equivalence of two methods.
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                      CXXMethodDecl *Method1,
@@ -2632,6 +2597,10 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
 
   D1 = D1->getCanonicalDecl();
   D2 = D2->getCanonicalDecl();
+
+  if (D1 == D2)
+    return true;
+
   std::pair<Decl *, Decl *> P{D1, D2};
 
   // Check whether we already know that these two declarations are not
@@ -2641,13 +2610,18 @@ static bool 
IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
     return false;
 
   // Check if a check for these declarations is already pending.
-  // If yes D1 and D2 will be checked later (from DeclsToCheck),
+  // If yes D1 and D2 will be checked later (from CurDeclsToCheck),
   // or these are already checked (and equivalent).
-  bool Inserted = Context.VisitedDecls.insert(P).second;
+  bool Inserted = Context.CurVisitedDecls->insert(P).second;
   if (!Inserted)
     return true;
 
-  Context.DeclsToCheck.push(P);
+  // We can also check if the pair is in VisitedDecls if we are currently
+  // checking attribute equivalence.
+  if (Context.isInAttrEquivalenceCheck() && Context.VisitedDecls.count(P))
+    return true;
+
+  Context.CurDeclsToCheck->push(P);
 
   return true;
 }
@@ -2826,11 +2800,11 @@ bool 
StructuralEquivalenceContext::CheckKindSpecificEquivalence(
   return true;
 }
 
-bool StructuralEquivalenceContext::Finish() {
-  while (!DeclsToCheck.empty()) {
+bool StructuralEquivalenceContext::checkDeclQueue() {
+  while (!CurDeclsToCheck->empty()) {
     // Check the next declaration.
-    std::pair<Decl *, Decl *> P = DeclsToCheck.front();
-    DeclsToCheck.pop();
+    std::pair<Decl *, Decl *> P = CurDeclsToCheck->front();
+    CurDeclsToCheck->pop();
 
     Decl *D1 = P.first;
     Decl *D2 = P.second;
@@ -2850,3 +2824,5 @@ bool StructuralEquivalenceContext::Finish() {
 
   return false;
 }
+
+bool StructuralEquivalenceContext::Finish() { return checkDeclQueue(); }
diff --git a/clang/lib/AST/AttrImpl.cpp b/clang/lib/AST/AttrImpl.cpp
index 5875a925d3fb0..3d994e06d2342 100644
--- a/clang/lib/AST/AttrImpl.cpp
+++ b/clang/lib/AST/AttrImpl.cpp
@@ -11,10 +11,12 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTStructuralEquivalence.h"
 #include "clang/AST/Attr.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/Type.h"
 #include <optional>
+#include <type_traits>
 using namespace clang;
 
 void LoopHintAttr::printPrettyPragma(raw_ostream &OS,
@@ -280,4 +282,84 @@ StringLiteral *FormatMatchesAttr::getFormatString() const {
   return cast<StringLiteral>(getExpectedFormat());
 }
 
+namespace {
+// Arguments whose types fail this test never compare equal unless there's a
+// specialization of equalAttrArgs for the type. Specilization for the 
following
+// arguments haven't been implemented yet:
+//  - DeclArgument
+//  - OMPTraitInfoArgument
+//  - VariadicOMPInteropInfoArgument
+template <class T> constexpr bool useDefaultEquality() {
+  return std::is_same_v<T, StringRef> || std::is_same_v<T, VersionTuple> ||
+         std::is_same_v<T, IdentifierInfo *> || std::is_same_v<T, ParamIdx> ||
+         std::is_same_v<T, Attr *> || std::is_same_v<T, char *> ||
+         std::is_enum_v<T> || std::is_integral_v<T>;
+}
+
+template <class T>
+typename std::enable_if_t<!useDefaultEquality<T>(), bool>
+equalAttrArgs(T A, T B, StructuralEquivalenceContext &Context) {
+  return false;
+}
+
+template <class T>
+typename std::enable_if_t<useDefaultEquality<T>(), bool>
+equalAttrArgs(T A1, T A2, StructuralEquivalenceContext &Context) {
+  return A1 == A2;
+}
+
+template <class T>
+bool equalAttrArgs(T *A1_B, T *A1_E, T *A2_B, T *A2_E,
+                   StructuralEquivalenceContext &Context) {
+  if (A1_E - A1_B != A2_E - A2_B)
+    return false;
+
+  for (; A1_B != A1_E; ++A1_B, ++A2_B)
+    if (!equalAttrArgs(*A1_B, *A2_B, Context))
+      return false;
+
+  return true;
+}
+
+template <>
+bool equalAttrArgs<Attr *>(Attr *A1, Attr *A2,
+                           StructuralEquivalenceContext &Context) {
+  return A1->isEquivalent(*A2, Context);
+}
+
+template <>
+bool equalAttrArgs<Expr *>(Expr *A1, Expr *A2,
+                           StructuralEquivalenceContext &Context) {
+  return ASTStructuralEquivalence::isEquivalent(Context, A1, A2);
+}
+
+template <>
+bool equalAttrArgs<QualType>(QualType T1, QualType T2,
+                             StructuralEquivalenceContext &Context) {
+  return ASTStructuralEquivalence::isEquivalent(Context, T1, T2);
+}
+
+template <>
+bool equalAttrArgs<const IdentifierInfo *>(
+    const IdentifierInfo *Name1, const IdentifierInfo *Name2,
+    StructuralEquivalenceContext &Context) {
+  return ASTStructuralEquivalence::isEquivalent(Name1, Name2);
+}
+
+bool areAlignedAttrsEqual(const AlignedAttr &A1, const AlignedAttr &A2,
+                          StructuralEquivalenceContext &Context) {
+  if (A1.getSpelling() != A2.getSpelling())
+    return false;
+
+  if (A1.isAlignmentExpr() != A2.isAlignmentExpr())
+    return false;
+
+  if (A1.isAlignmentExpr())
+    return equalAttrArgs(A1.getAlignmentExpr(), A2.getAlignmentExpr(), 
Context);
+
+  return equalAttrArgs(A1.getAlignmentType()->getType(),
+                       A2.getAlignmentType()->getType(), Context);
+}
+} // namespace
+
 #include "clang/AST/AttrImpl.inc"
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 30c6d3ed91f1e..0a1e442656c35 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -711,7 +711,7 @@ static AvailabilityResult CheckAvailability(ASTContext 
&Context,
   // Make sure that this declaration has already been introduced.
   if (!A->getIntroduced().empty() &&
       EnclosingVersion < A->getIntroduced()) {
-    IdentifierInfo *IIEnv = A->getEnvironment();
+    const IdentifierInfo *IIEnv = A->getEnvironment();
     auto &Triple = Context.getTargetInfo().getTriple();
     StringRef TargetEnv = Triple.getEnvironmentName();
     StringRef EnvName =
diff --git a/clang/lib/Index/CommentToXML.cpp b/clang/lib/Index/CommentToXML.cpp
index f54d8be790217..f396760126fcc 100644
--- a/clang/lib/Index/CommentToXML.cpp
+++ b/clang/lib/Index/CommentToXML.cpp
@@ -1065,7 +1065,7 @@ void CommentASTToXMLConverter::visitFullComment(const 
FullComment *C) {
       if (AA->getUnavailable())
         Result << "<Unavailable/>";
 
-      IdentifierInfo *Environment = AA->getEnvironment();
+      const IdentifierInfo *Environment = AA->getEnvironment();
       if (Environment) {
         Result << "<Environment>" << Environment->getName() << 
"</Environment>";
       }
diff --git a/clang/lib/Sema/SemaAvailability.cpp 
b/clang/lib/Sema/SemaAvailability.cpp
index b09e1684e4e64..3624e487a7572 100644
--- a/clang/lib/Sema/SemaAvailability.cpp
+++ b/clang/lib/Sema/SemaAvailability.cpp
@@ -33,7 +33,7 @@ using namespace sema;
 
 static bool hasMatchingEnvironmentOrNone(const ASTContext &Context,
                                          const AvailabilityAttr *AA) {
-  IdentifierInfo *IIEnvironment = AA->getEnvironment();
+  const IdentifierInfo *IIEnvironment = AA->getEnvironment();
   auto Environment = Context.getTargetInfo().getTriple().getEnvironment();
   if (!IIEnvironment || Environment == llvm::Triple::UnknownEnvironment)
     return true;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index e3af5023c74d0..976b90c0c1bf1 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -1938,7 +1938,7 @@ static void handleCPUSpecificAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
   if (!AL.checkAtLeastNumArgs(S, 1))
     return;
 
-  SmallVector<IdentifierInfo *, 8> CPUs;
+  SmallVector<const IdentifierInfo *, 8> CPUs;
   for (unsigned ArgNo = 0; ArgNo < getNumAttributeArgs(AL); ++ArgNo) {
     if (!AL.isArgIdent(ArgNo)) {
       S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
@@ -2292,7 +2292,7 @@ static void handleAttrWithMessage(Sema &S, Decl *D, const 
ParsedAttr &AL) {
 }
 
 static bool checkAvailabilityAttr(Sema &S, SourceRange Range,
-                                  IdentifierInfo *Platform,
+                                  const IdentifierInfo *Platform,
                                   VersionTuple Introduced,
                                   VersionTuple Deprecated,
                                   VersionTuple Obsoleted) {
@@ -2349,11 +2349,11 @@ static bool versionsMatch(const VersionTuple &X, const 
VersionTuple &Y,
 }
 
 AvailabilityAttr *Sema::mergeAvailabilityAttr(
-    NamedDecl *D, const AttributeCommonInfo &CI, IdentifierInfo *Platform,
+    NamedDecl *D, const AttributeCommonInfo &CI, const IdentifierInfo 
*Platform,
     bool Implicit, VersionTuple Introduced, VersionTuple Deprecated,
     VersionTuple Obsoleted, bool IsUnavailable, StringRef Message,
     bool IsStrict, StringRef Replacement, AvailabilityMergeKind AMK,
-    int Priority, IdentifierInfo *Environment) {
+    int Priority, const IdentifierInfo *Environment) {
   VersionTuple MergedIntroduced = Introduced;
   VersionTuple MergedDeprecated = Deprecated;
   VersionTuple MergedObsoleted = Obsoleted;
@@ -2381,13 +2381,13 @@ AvailabilityAttr *Sema::mergeAvailabilityAttr(
         continue;
       }
 
-      IdentifierInfo *OldPlatform = OldAA->getPlatform();
+      const IdentifierInfo *OldPlatform = OldAA->getPlatform();
       if (OldPlatform != Platform) {
         ++i;
         continue;
       }
 
-      IdentifierInfo *OldEnvironment = OldAA->getEnvironment();
+      const IdentifierInfo *OldEnvironment = OldAA->getEnvironment();
       if (OldEnvironment != Environment) {
         ++i;
         continue;
@@ -3783,7 +3783,7 @@ ErrorAttr *Sema::mergeErrorAttr(Decl *D, const 
AttributeCommonInfo &CI,
 }
 
 FormatAttr *Sema::mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
-                                  IdentifierInfo *Format, int FormatIdx,
+                                  const IdentifierInfo *Format, int FormatIdx,
                                   int FirstArg) {
   // Check whether we already have an equivalent format attribute.
   for (auto *F : D->specific_attrs<FormatAttr>()) {
@@ -3803,7 +3803,7 @@ FormatAttr *Sema::mergeFormatAttr(Decl *D, const 
AttributeCommonInfo &CI,
 
 FormatMatchesAttr *Sema::mergeFormatMatchesAttr(Decl *D,
                                                 const AttributeCommonInfo &CI,
-                                                IdentifierInfo *Format,
+                                                const IdentifierInfo *Format,
                                                 int FormatIdx,
                                                 StringLiteral *FormatStr) {
   // Check whether we already have an equivalent FormatMatches attribute.
@@ -4757,7 +4757,7 @@ static void handleModeAttr(Sema &S, Decl *D, const 
ParsedAttr &AL) {
 }
 
 void Sema::AddModeAttr(Decl *D, const AttributeCommonInfo &CI,
-                       IdentifierInfo *Name, bool InInstantiation) {
+                       const IdentifierInfo *Name, bool InInstantiation) {
   StringRef Str = Name->getName();
   normalizeName(Str);
   SourceLocation AttrLoc = CI.getLoc();
diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp
index 4daf01703d7dd..5dbd64b65c015 100644
--- a/clang/lib/Sema/SemaExprObjC.cpp
+++ b/clang/lib/Sema/SemaExprObjC.cpp
@@ -4007,7 +4007,7 @@ static bool CheckObjCBridgeNSCast(Sema &S, QualType 
castType, Expr *castExpr,
   while (const auto *TD = T->getAs<TypedefType>()) {
     TypedefNameDecl *TDNDecl = TD->getDecl();
     if (TB *ObjCBAttr = getObjCBridgeAttr<TB>(TD)) {
-      if (IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) {
+      if (const IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) {
         HadTheAttribute = true;
         if (Parm->isStr("id"))
           return true;
@@ -4070,7 +4070,7 @@ static bool CheckObjCBridgeCFCast(Sema &S, QualType 
castType, Expr *castExpr,
   while (const auto *TD = T->getAs<TypedefType>()) {
     TypedefNameDecl *TDNDecl = TD->getDecl();
     if (TB *ObjCBAttr = getObjCBridgeAttr<TB>(TD)) {
-      if (IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) {
+      if (const IdentifierInfo *Parm = ObjCBAttr->getBridgedType()) {
         HadTheAttribute = true;
         if (Parm->isStr("id"))
           return true;
@@ -4228,9 +4228,9 @@ bool SemaObjC::checkObjCBridgeRelatedComponents(
   if (!ObjCBAttr)
     return false;
 
-  IdentifierInfo *RCId = ObjCBAttr->getRelatedClass();
-  IdentifierInfo *CMId = ObjCBAttr->getClassMethod();
-  IdentifierInfo *IMId = ObjCBAttr->getInstanceMethod();
+  const IdentifierInfo *RCId = ObjCBAttr->getRelatedClass();
+  const IdentifierInfo *CMId = ObjCBAttr->getClassMethod();
+  const IdentifierInfo *IMId = ObjCBAttr->getInstanceMethod();
   if (!RCId)
     return false;
   NamedDecl *Target = nullptr;
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index e7ee3b1adf941..a17fdf9db4568 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -2579,7 +2579,7 @@ void DiagnoseHLSLAvailability::RunOnFunction(const 
FunctionDecl *FD) {
 
 bool DiagnoseHLSLAvailability::HasMatchingEnvironmentOrNone(
     const AvailabilityAttr *AA) {
-  IdentifierInfo *IIEnvironment = AA->getEnvironment();
+  const IdentifierInfo *IIEnvironment = AA->getEnvironment();
   if (!IIEnvironment)
     return true;
 
@@ -2623,7 +2623,7 @@ void 
DiagnoseHLSLAvailability::CheckDeclAvailability(NamedDecl *D,
                                                      const AvailabilityAttr 
*AA,
                                                      SourceRange Range) {
 
-  IdentifierInfo *IIEnv = AA->getEnvironment();
+  const IdentifierInfo *IIEnv = AA->getEnvironment();
 
   if (!IIEnv) {
     // The availability attribute does not have environment -> it depends only
diff --git a/clang/lib/Sema/SemaObjC.cpp b/clang/lib/Sema/SemaObjC.cpp
index 7aaa56e37b3be..dae30b7e941d1 100644
--- a/clang/lib/Sema/SemaObjC.cpp
+++ b/clang/lib/Sema/SemaObjC.cpp
@@ -1468,7 +1468,7 @@ bool SemaObjC::isCFError(RecordDecl *RD) {
   // declared with "objc_bridge_mutable", so look for either one of the two
   // attributes.
   if (RD->getTagKind() == TagTypeKind::Struct) {
-    IdentifierInfo *bridgedType = nullptr;
+    const IdentifierInfo *bridgedType = nullptr;
     if (auto bridgeAttr = RD->getAttr<ObjCBridgeAttr>())
       bridgedType = bridgeAttr->getBridgedType();
     else if (auto bridgeAttr = RD->getAttr<ObjCBridgeMutableAttr>())
diff --git a/clang/test/C/C23/n3037.c b/clang/test/C/C23/n3037.c
index 0a82e8846d92a..4200c07eba88b 100644
--- a/clang/test/C/C23/n3037.c
+++ b/clang/test/C/C23/n3037.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -Wall -Wno-comment 
-verify=both,c23 %s
-// RUN: %clang_cc1 -fsyntax-only -std=c17 -pedantic -Wall -Wno-comment 
-Wno-c23-extensions -verify=both,c17 %s
+// RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -Wall -Wno-comment 
-fexperimental-late-parse-attributes -verify=both,c23 %s
+// RUN: %clang_cc1 -fsyntax-only -std=c17 -pedantic -Wall -Wno-comment 
-Wno-c23-extensions -fexperimental-late-parse-attributes -verify=both,c17 %s
 
 /* WG14 N3037: Clang 21
  * Improved tag compatibility
@@ -8,6 +8,8 @@
  * paper made identical tag types compatible within the same TU.
  */
 
+int g0;
+
 struct foo { int a; } p;
 
 void baz(struct foo f); // c17-note {{passing argument to parameter 'f' here}}
@@ -93,14 +95,11 @@ struct [[gnu::packed]] attr_test_2 { // c17-error 
{{redefinition of 'attr_test_2
 };
 
 // This includes the same attribute on both types.
-struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is 
here}} \
-                                       c23-note {{attribute 'gnu::packed' 
here}}
+struct [[gnu::packed]] attr_test_3 { // c17-note {{previous definition is 
here}}
   int x;
 };
 
-struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 
'attr_test_3'}} \
-                                        c23-error {{type 'struct attr_test_3' 
has an attribute which currently causes the types to be treated as though they 
are incompatible}} \
-                                        c23-note {{attribute 'gnu::packed' 
here}}
+struct [[gnu::packed]] attr_test_3 { // c17-error {{redefinition of 
'attr_test_3'}}
   int x;
 };
 
@@ -128,13 +127,12 @@ struct field_attr_test_2 { // c17-error {{redefinition of 
'field_attr_test_2'}}
 };
 
 struct field_attr_test_3 { // c17-note {{previous definition is here}}
-  [[gnu::packed]] int x;   // c23-note {{attribute 'gnu::packed' here}}
+  [[gnu::packed]] int x;
   int y;
 };
 
-struct field_attr_test_3 { // c17-error {{redefinition of 
'field_attr_test_3'}} \
-                              c23-error {{type 'struct field_attr_test_3' has 
a member with an attribute which currently causes the types to be treated as 
though they are incompatible}}
-  int x [[gnu::packed]];   // c23-note {{attribute 'gnu::packed' here}}
+struct field_attr_test_3 { // c17-error {{redefinition of 'field_attr_test_3'}}
+  int x [[gnu::packed]];
   int y;
 };
 
@@ -272,18 +270,12 @@ enum Z1 { ZC = 1 }; // both-note {{previous definition is 
here}}
 enum Z2 { ZC = 1 }; // both-error {{redefinition of enumerator 'ZC'}}
 
 // Test attributes on the enumeration and enumerators.
-enum [[deprecated]] enum_attr_test_1 { // c17-note {{previous definition is 
here}} \
-                                          c23-note {{attribute 'deprecated' 
here}}
-  EAT1 [[deprecated]] // c17-note {{previous definition is here}} \
-                         c23-note {{attribute 'deprecated' here}}
+enum [[deprecated]] enum_attr_test_1 { // c17-note {{previous definition is 
here}}
+  EAT1 [[deprecated]] // c17-note {{previous definition is here}}
 };
 
-enum [[deprecated]] enum_attr_test_1 { // c17-error {{redefinition of 
'enum_attr_test_1'}} \
-                                          c23-error {{type 'enum 
enum_attr_test_1' has an attribute which currently causes the types to be 
treated as though they are incompatible}} \
-                                          c23-error {{type 'enum 
enum_attr_test_1' has a member with an attribute which currently causes the 
types to be treated as though they are incompatible}} \
-                                          c23-note {{attribute 'deprecated' 
here}}
-  EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}} \
-                         c23-note {{attribute 'deprecated' here}}
+enum [[deprecated]] enum_attr_test_1 { // c17-error {{redefinition of 
'enum_attr_test_1'}}
+  EAT1 [[deprecated]] // c17-error {{redefinition of enumerator 'EAT1'}}
 };
 
 enum [[deprecated]] enum_attr_test_2 { // c17-note {{previous definition is 
here}} \
@@ -364,8 +356,18 @@ struct array { int y; int x[0]; };   // c17-error 
{{redefinition of 'array'}} \
 struct array_2 { int y; int x[3]; };         // c17-note {{previous definition 
is here}}
 struct array_2 { int y; int x[1 + 1 + 1]; }; // c17-error {{redefinition of 
'array_2'}}
 
-struct alignment { // c17-note {{previous definition is here}}
-  _Alignas(int) int x; // c23-note {{attribute '_Alignas' here}}
+struct alignment { // c17-note 4 {{previous definition is here}}
+  _Alignas(int) int x; // c23-note 2 {{attribute '_Alignas' here}}
+};
+
+struct alignment { // c17-error {{redefinition of 'alignment'}}
+  _Alignas(int) int x;
+};
+
+typedef int MyInt;
+
+struct alignment { // c17-error {{redefinition of 'alignment'}}
+  _Alignas(MyInt) int x;
 };
 
 struct alignment { // c17-error {{redefinition of 'alignment'}} \
@@ -373,6 +375,29 @@ struct alignment { // c17-error {{redefinition of 
'alignment'}} \
   int x;
 };
 
+struct alignment { // c17-error {{redefinition of 'alignment'}} \
+                      c23-error {{type 'struct alignment' has a member with an 
attribute which currently causes the types to be treated as though they are 
incompatible}}
+  _Alignas(4) int x; // c23-note {{attribute '_Alignas' here}}
+};
+
+struct alignment2 { // c17-note 3 {{previous definition is here}}
+  _Alignas(4) int x; // c23-note 2 {{attribute '_Alignas' here}}
+};
+
+struct alignment2 { // c17-error {{redefinition of 'alignment2'}}
+  _Alignas(4) int x;
+};
+
+struct alignment2 { // c17-error {{redefinition of 'alignment2'}} \
+                       c23-error {{type 'struct alignment2' has a member with 
an attribute which currently causes the types to be treated as though they are 
incompatible}}
+  _Alignas(4 * 1) int x; // c23-note {{attribute '_Alignas' here}}
+};
+
+struct alignment2 { // c17-error {{redefinition of 'alignment2'}} \
+                       c23-error {{type 'struct alignment2' has a member with 
an attribute which currently causes the types to be treated as though they are 
incompatible}}
+  _Alignas(8) int x; // c23-note {{attribute '_Alignas' here}}
+};
+
 // Both structures need to have a tag in order to be compatible within the same
 // translation unit.
 struct     {int i;} nontag;
@@ -552,15 +577,12 @@ struct __attribute__((availability(ios, introduced = 14), 
availability(macos, in
 
 enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) 
AvailE0 {
   // c17-note@-1 {{previous definition is here}}
-  // c23-note@-2 {{attribute 'warn_unused_result' here}}
   A_E0
   // c17-note@-1 {{previous definition is here}}
 };
 
 enum __attribute__((availability(macos, introduced = 12), warn_unused_result)) 
AvailE0 {
   // c17-error@-1 {{redefinition of 'AvailE0'}}
-  // c23-error@-2 {{type 'enum AvailE0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
-  // c23-note@-3 {{attribute 'warn_unused_result' here}}
   A_E0
   // c17-error@-1 {{redefinition of enumerator 'A_E0'}}
 };
@@ -577,28 +599,296 @@ enum __attribute__((enum_extensibility(closed))) AvailE1 
{
   // c17-error@-1 {{redefinition of enumerator 'A_E1'}}
 };
 
-struct [[__maybe_unused__]] AvailS1;
-
-struct __attribute__((availability(macos, introduced = 12))) AvailS1 {
+#pragma clang attribute push (__attribute__((availability(macos, 
introduced=12))), apply_to=record)
+struct AvailS1 {
   // c17-note@-1 {{previous definition is here}}
+  // c23-note@-3 {{attribute 'availability' here}}
   int a;
 };
+#pragma clang attribute pop
 
 struct __attribute__((availability(macos, introduced = 12))) AvailS1 {
   // c17-error@-1 {{redefinition of 'AvailS1'}}
+  // c23-error@-2 {{type 'struct AvailS1' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'availability' here}}
   int a;
 };
 
-struct __attribute__((warn_unused_result)) AvailS2;
-// c23-note@-1 {{attribute 'warn_unused_result' here}}
-
-struct __attribute__((availability(macos, introduced = 12))) AvailS2 {
+struct __attribute__((availability(macos, introduced = 12, message = "abc"))) 
AvailS2 {
   // c17-note@-1 {{previous definition is here}}
+  // c23-note@-2 {{attribute 'availability' here}}
   int a;
 };
 
-struct __attribute__((availability(macos, introduced = 12))) AvailS2 {
+struct __attribute__((availability(macos, introduced = 12, message = "xyz"))) 
AvailS2 {
   // c17-error@-1 {{redefinition of 'AvailS2'}}
   // c23-error@-2 {{type 'struct AvailS2' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'availability' here}}
+  int a;
+};
+
+struct __attribute__((availability(macos, introduced = 12, strict))) AvailS3 {
+  // c17-note@-1 {{previous definition is here}}
+  // c23-note@-2 {{attribute 'availability' here}}
+  int a;
+};
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS3 {
+  // c17-error@-1 {{redefinition of 'AvailS3'}}
+  // c23-error@-2 {{type 'struct AvailS3' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'availability' here}}
+  int a;
+};
+
+struct __attribute__((availability(macos, introduced = 12))) AvailS4 {
+  // c17-note@-1 {{previous definition is here}}
+  // c23-note@-2 {{attribute 'availability' here}}
+  int a;
+};
+
+struct __attribute__((availability(ios, introduced = 12))) AvailS4 {
+  // c17-error@-1 {{redefinition of 'AvailS4'}}
+  // c23-error@-2 {{type 'struct AvailS4' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'availability' here}}
+  int a;
+};
+
+struct PreferredType0 {
+  // c17-note@-1 3 {{previous definition is here}}
+  [[clang::preferred_type(_Bool)]] int f : 1;
+  // c23-note@-1 {{attribute 'clang::preferred_type' here}}
+};
+
+struct PreferredType0 {
+  // c17-error@-1 {{redefinition of 'PreferredType0'}}
+  [[clang::preferred_type(_Bool)]] int f : 1;
+};
+
+typedef _Bool MyBool;
+
+struct PreferredType0 {
+  // c17-error@-1 {{redefinition of 'PreferredType0'}}
+  [[clang::preferred_type(MyBool)]] int f : 1;
+};
+
+struct PreferredType0 {
+  // c17-error@-1 {{redefinition of 'PreferredType0'}}
+  // c23-error@-2 {{type 'struct PreferredType0' has a member with an 
attribute which currently causes the types to be treated as though they are 
incompatible}}
+  [[clang::preferred_type(char)]] int f : 1;
+  // c23-note@-1 {{attribute 'clang::preferred_type' here}}
+};
+
+struct __attribute__((abi_tag("a", "b"))) ABITag0 {
+  // c17-note@-1 4 {{previous definition is here}}
+  // c23-note@-2 3 {{attribute 'abi_tag' here}}
+  int f;
+};
+
+struct __attribute__((abi_tag("a", "b"))) ABITag0 {
+  // c17-error@-1 {{redefinition of 'ABITag0'}}
+  int f;
+};
+
+struct __attribute__((abi_tag("a"))) ABITag0 {
+  // c17-error@-1 {{redefinition of 'ABITag0'}}
+  // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'abi_tag' here}}
+  int f;
+};
+
+struct __attribute__((abi_tag("a", "b", "c"))) ABITag0 {
+  // c17-error@-1 {{redefinition of 'ABITag0'}}
+  // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'abi_tag' here}}
+  int f;
+};
+
+struct __attribute__((abi_tag("a", "d"))) ABITag0 {
+  // c17-error@-1 {{redefinition of 'ABITag0'}}
+  // c23-error@-2 {{type 'struct ABITag0' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'abi_tag' here}}
+  int f;
+};
+
+struct CountedBy0 {
+  // c17-note@-1 {{previous definition is here}}
+  int count;
+  int * __attribute__((counted_by(count))) p;
+};
+
+struct CountedBy0 {
+  // c17-error@-1 {{redefinition of 'CountedBy0'}}
+  int count;
+  int * __attribute__((counted_by(count))) p;
+};
+
+struct CountedBy1 {
+  // c17-note@-1 {{previous definition is here}}
+  int count0, count1;
+  int * __attribute__((counted_by(count0))) p;
+};
+
+struct CountedBy1 {
+  // c17-error@-1 {{redefinition of 'CountedBy1'}}
+  int count0, count1;
+  // FIXME: This should be rejected in c23.
+  int * __attribute__((counted_by(count1))) p;
+};
+
+struct __attribute__ ((lockable)) Lock {
+  int a;
+};
+
+struct GuardedBy0 {
+  // c17-note@-1 {{previous definition is here}}
+  struct Lock lock;
+  int b __attribute__((guarded_by(lock)));
+};
+
+struct GuardedBy0 {
+  // c17-error@-1 {{redefinition of 'GuardedBy0'}}
+  struct Lock lock;
+  int b __attribute__((guarded_by(lock)));
+};
+
+struct GuardedBy1 {
+  // c17-note@-1 {{previous definition is here}}
+  struct Lock lock0, lock1;
+  int b __attribute__((guarded_by(lock0)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct GuardedBy1 {
+  // c17-error@-1 {{redefinition of 'GuardedBy1'}}
+  // c23-error@-2 {{type 'struct GuardedBy1' has a member with an attribute 
which currently causes the types to be treated as though they are incompatible}}
+  struct Lock lock0, lock1;
+  int b __attribute__((guarded_by(lock1)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct Lock sharedLock0, sharedLock1;
+
+struct GuardedBy2 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(sharedLock0)));
+};
+
+struct GuardedBy2 {
+  // c17-error@-1 {{redefinition of 'GuardedBy2'}}
+  int b __attribute__((guarded_by(sharedLock0)));
+};
+
+struct GuardedBy3 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(sharedLock0)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct GuardedBy3 {
+  // c17-error@-1 {{redefinition of 'GuardedBy3'}}
+  // c23-error@-2 {{type 'struct GuardedBy3' has a member with an attribute 
which currently causes the types to be treated as though they are incompatible}}
+  int b __attribute__((guarded_by(sharedLock1)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct OuterLock {
+  struct Lock lock;
+};
+
+struct OuterLock outer0, outer1;
+
+struct GuardedBy4 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(outer0.lock)));
+};
+
+struct GuardedBy4 {
+  // c17-error@-1 {{redefinition of 'GuardedBy4'}}
+  int b __attribute__((guarded_by(outer0.lock)));
+};
+
+struct GuardedBy5 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(outer0.lock)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct GuardedBy5 {
+  // c17-error@-1 {{redefinition of 'GuardedBy5'}}
+  // c23-error@-2 {{type 'struct GuardedBy5' has a member with an attribute 
which currently causes the types to be treated as though they are incompatible}}
+  int b __attribute__((guarded_by(outer1.lock)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+};
+
+struct GuardedBy6 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(lock)));
+  struct Lock lock;
+};
+
+struct GuardedBy6 {
+  // c17-error@-1 {{redefinition of 'GuardedBy6'}}
+  int b __attribute__((guarded_by(lock)));
+  struct Lock lock;
+};
+
+struct GuardedBy7 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(lock0)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+  struct Lock lock0, lock1;
+};
+
+struct GuardedBy7 {
+  // c17-error@-1 {{redefinition of 'GuardedBy7'}}
+  // c23-error@-2 {{type 'struct GuardedBy7' has a member with an attribute 
which currently causes the types to be treated as though they are incompatible}}
+  int b __attribute__((guarded_by(lock1)));
+  // c23-note@-1 {{attribute 'guarded_by' here}}
+  struct Lock lock0, lock1;
+};
+
+struct GuardedBy8 {
+  // c17-note@-1 {{previous definition is here}}
+  int b __attribute__((guarded_by(lock)));
+  struct {
+    struct Lock lock;
+  };
+};
+
+struct GuardedBy8 {
+  // c17-error@-1 {{redefinition of 'GuardedBy8'}}
+  int b __attribute__((guarded_by(lock)));
+  struct {
+    struct Lock lock;
+  };
+};
+
+struct __attribute__((annotate("abc", &baz, &g0))) Annotate0 {
+  // c17-note@-1 {{previous definition is here}}
+  int a;
+};
+
+struct __attribute__((annotate("abc", &baz, &g0))) Annotate0 {
+  // c17-error@-1 {{redefinition of 'Annotate0'}}
+  int a;
+};
+
+struct __attribute__((annotate("abc", &baz, &g0))) Annotate1 {
+  // c17-note@-1 2 {{previous definition is here}}
+  // c23-note@-2 2 {{attribute 'annotate' here}}
+  int a;
+};
+
+struct __attribute__((annotate("abc", &bar, &g0))) Annotate1 {
+  // c17-error@-1 {{redefinition of 'Annotate1'}}
+  // c23-error@-2 {{type 'struct Annotate1' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'annotate' here}}
+  int a;
+};
+
+struct __attribute__((annotate("abc", &baz, &g0, 2))) Annotate1 {
+  // c17-error@-1 {{redefinition of 'Annotate1'}}
+  // c23-error@-2 {{type 'struct Annotate1' has an attribute which currently 
causes the types to be treated as though they are incompatible}}
+  // c23-note@-3 {{attribute 'annotate' here}}
   int a;
 };
diff --git a/clang/utils/TableGen/ClangAttrEmitter.cpp 
b/clang/utils/TableGen/ClangAttrEmitter.cpp
index bee9a01a3b01a..36c0b2b1c5b9c 100644
--- a/clang/utils/TableGen/ClangAttrEmitter.cpp
+++ b/clang/utils/TableGen/ClangAttrEmitter.cpp
@@ -130,7 +130,7 @@ static std::string ReadPCHRecord(StringRef type) {
       .EndsWith("Decl *", "Record.readDeclAs<" + type.drop_back().str() + 
">()")
       .Case("TypeSourceInfo *", "Record.readTypeSourceInfo()")
       .Case("Expr *", "Record.readExpr()")
-      .Case("IdentifierInfo *", "Record.readIdentifier()")
+      .Case("const IdentifierInfo *", "Record.readIdentifier()")
       .Case("StringRef", "Record.readString()")
       .Case("ParamIdx", "ParamIdx::deserialize(Record.readInt())")
       .Case("OMPTraitInfo *", "Record.readOMPTraitInfo()")
@@ -152,7 +152,7 @@ static std::string WritePCHRecord(StringRef type, StringRef 
name) {
              .Case("TypeSourceInfo *",
                    "AddTypeSourceInfo(" + name.str() + ");\n")
              .Case("Expr *", "AddStmt(" + name.str() + ");\n")
-             .Case("IdentifierInfo *",
+             .Case("const IdentifierInfo *",
                    "AddIdentifierRef(" + name.str() + ");\n")
              .Case("StringRef", "AddString(" + name.str() + ");\n")
              .Case("ParamIdx", "push_back(" + name.str() + ".serialize());\n")
@@ -280,6 +280,13 @@ namespace {
     virtual void writeImplicitCtorArgs(raw_ostream &OS) const {
       OS << getUpperName();
     }
+
+    constexpr StringRef getArgEqualityFn() const { return "equalAttrArgs"; }
+
+    virtual std::string emitAttrArgEqualityCheck() const {
+      std::string S = std::string("get") + std::string(getUpperName()) + "()";
+      return getArgEqualityFn().str() + "(" + S + ", Other." + S + ", 
Context)";
+    }
   };
 
   class SimpleArgument : public Argument {
@@ -340,7 +347,7 @@ namespace {
         return ((subject == list) || ...);
       };
 
-      if (IsOneOf(type, "IdentifierInfo *", "Expr *"))
+      if (IsOneOf(type, "const IdentifierInfo *", "Expr *"))
         return "!get" + getUpperName().str() + "()";
       if (IsOneOf(type, "TypeSourceInfo *"))
         return "!get" + getUpperName().str() + "Loc()";
@@ -356,7 +363,7 @@ namespace {
       if (type == "FunctionDecl *")
         OS << "\" << get" << getUpperName()
            << "()->getNameInfo().getAsString() << \"";
-      else if (type == "IdentifierInfo *")
+      else if (type == "const IdentifierInfo *")
         // Some non-optional (comma required) identifier arguments can be the
         // empty string but are then recorded as a nullptr.
         OS << "\" << (get" << getUpperName() << "() ? get" << getUpperName()
@@ -375,7 +382,7 @@ namespace {
       if (StringRef(type).ends_with("Decl *")) {
         OS << "    OS << \" \";\n";
         OS << "    dumpBareDeclRef(SA->get" << getUpperName() << "());\n";
-      } else if (type == "IdentifierInfo *") {
+      } else if (type == "const IdentifierInfo *") {
         // Some non-optional (comma required) identifier arguments can be the
         // empty string but are then recorded as a nullptr.
         OS << "    if (SA->get" << getUpperName() << "())\n"
@@ -679,6 +686,17 @@ namespace {
     void writeHasChildren(raw_ostream &OS) const override {
       OS << "SA->is" << getUpperName() << "Expr()";
     }
+
+    std::string emitAttrArgEqualityCheck() const override {
+      auto GetStr = [&](bool Other) {
+        std::string CtxStr = Other ? "Context.ToCtx" : "Context.FromCtx";
+        std::string S = std::string("get") + std::string(getUpperName()) + "(" 
+
+                        CtxStr + ")";
+        return S;
+      };
+      return getArgEqualityFn().str() + "(" + GetStr(false) + ", Other." +
+             GetStr(true) + ", Context)";
+    }
   };
 
   class VariadicArgument : public Argument {
@@ -836,6 +854,19 @@ namespace {
       OS << "    for (const auto &Val : SA->" << RangeName << "())\n";
       writeDumpImpl(OS);
     }
+
+    std::string emitAttrArgEqualityCheck() const override {
+      auto GenIter = [&](bool IsOther, const std::string &Suffix) {
+        std::string S = IsOther ? "Other." : "";
+        std::string LN = getLowerName().str();
+        S += LN + "_" + Suffix + "()";
+        return S;
+      };
+
+      return getArgEqualityFn().str() + "(" + GenIter(false, "begin") + ", " +
+             GenIter(false, "end") + ", " + GenIter(true, "begin") + ", " +
+             GenIter(true, "end") + ", Context)";
+    }
   };
 
   class VariadicOMPInteropInfoArgument : public VariadicArgument {
@@ -1371,7 +1402,7 @@ namespace {
   class VariadicIdentifierArgument : public VariadicArgument {
   public:
     VariadicIdentifierArgument(const Record &Arg, StringRef Attr)
-      : VariadicArgument(Arg, Attr, "IdentifierInfo *")
+      : VariadicArgument(Arg, Attr, "const IdentifierInfo *")
     {}
   };
 
@@ -1481,7 +1512,7 @@ createArgument(const Record &Arg, StringRef Attr,
     Ptr = std::make_unique<SimpleArgument>(
         Arg, Attr, (Arg.getValueAsDef("Kind")->getName() + "Decl *").str());
   else if (ArgName == "IdentifierArgument")
-    Ptr = std::make_unique<SimpleArgument>(Arg, Attr, "IdentifierInfo *");
+    Ptr = std::make_unique<SimpleArgument>(Arg, Attr, "const IdentifierInfo 
*");
   else if (ArgName == "DefaultBoolArgument")
     Ptr = std::make_unique<DefaultSimpleArgument>(
         Arg, Attr, "bool", Arg.getValueAsBit("Default"));
@@ -3148,6 +3179,28 @@ static void emitAttributes(const RecordKeeper &Records, 
raw_ostream &OS,
             OS, Header);
     }
 
+    std::string FnStr = "isEquivalent(const ";
+    FnStr += R.getName();
+    FnStr += "Attr &Other, StructuralEquivalenceContext &Context) const";
+    if (Header) {
+      OS << "  bool " << FnStr << ";\n";
+    } else {
+      OS << "bool " << R.getName() << "Attr::" << FnStr << " {\n";
+      std::string CustomFn = R.getValueAsString("comparisonFn").str();
+      if (CustomFn.empty()) {
+        if (!ElideSpelling)
+          OS << "  if (getSpelling() != Other.getSpelling()) return 
false;\n\n";
+        for (const auto &ai : Args) {
+          OS << "  if (!" << ai->emitAttrArgEqualityCheck() << ")\n";
+          OS << "    return false;\n";
+        }
+        OS << "  return true;\n";
+      } else {
+        OS << "  return " + CustomFn + "(*this, Other, Context);\n";
+      }
+      OS << "}\n\n";
+    }
+
     if (Header) {
       if (DelayedArgs) {
         DelayedArgs->writeAccessors(OS);
@@ -3200,6 +3253,26 @@ void clang::EmitClangAttrClass(const RecordKeeper 
&Records, raw_ostream &OS) {
   OS << "#endif // LLVM_CLANG_ATTR_CLASSES_INC\n";
 }
 
+static void emitEquivalenceFunction(const RecordKeeper &Records,
+                                    raw_ostream &OS) {
+  OS << "bool Attr::isEquivalent(const Attr &Other, "
+        "StructuralEquivalenceContext &Context) const {\n";
+  OS << "if (getKind() != Other.getKind()) return false;\n\n";
+  OS << "  switch (getKind()) {\n";
+  for (const auto *Attr : Records.getAllDerivedDefinitions("Attr")) {
+    const Record &R = *Attr;
+    if (!R.getValueAsBit("ASTNode"))
+      continue;
+
+    OS << "  case attr::" << R.getName() << ":\n";
+    OS << "    return cast<" << R.getName() << 
"Attr>(this)->isEquivalent(cast<"
+       << R.getName() << "Attr>(Other), Context);\n";
+  }
+  OS << "  }\n";
+  OS << "  llvm_unreachable(\"Unexpected attribute kind!\");\n";
+  OS << "}\n\n";
+}
+
 // Emits the class method definitions for attributes.
 void clang::EmitClangAttrImpl(const RecordKeeper &Records, raw_ostream &OS) {
   emitSourceFileHeader("Attribute classes' member function definitions", OS,
@@ -3234,6 +3307,8 @@ void clang::EmitClangAttrImpl(const RecordKeeper 
&Records, raw_ostream &OS) {
   OS << "void Attr::printPretty(raw_ostream &OS, "
         "const PrintingPolicy &Policy) const {\n";
   EmitFunc("printPretty(OS, Policy)");
+
+  emitEquivalenceFunction(Records, OS);
 }
 
 static void emitAttrList(raw_ostream &OS, StringRef Class,

>From d30dd3d14a1eee675b40a517f7a3a411355a747b Mon Sep 17 00:00:00 2001
From: Akira Hatanaka <[email protected]>
Date: Mon, 22 Dec 2025 06:44:42 -0800
Subject: [PATCH 5/5] Constify IdentifierInfo in more places

---
 clang/include/clang/Sema/Sema.h | 2 +-
 clang/lib/Sema/SemaDeclAttr.cpp | 9 ++++-----
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f3837090ac349..c9ad6860dc625 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4960,7 +4960,7 @@ class Sema final : public SemaBase {
                                             StringLiteral *FormatStr);
   ModularFormatAttr *mergeModularFormatAttr(Decl *D,
                                             const AttributeCommonInfo &CI,
-                                            IdentifierInfo *ModularImplFn,
+                                            const IdentifierInfo 
*ModularImplFn,
                                             StringRef ImplName,
                                             MutableArrayRef<StringRef> 
Aspects);
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 71bbc938aa498..263ce2118ba86 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7004,7 +7004,7 @@ static void handleVTablePointerAuthentication(Sema &S, 
Decl *D,
 }
 
 static bool modularFormatAttrsEquiv(const ModularFormatAttr *Existing,
-                                    IdentifierInfo *ModularImplFn,
+                                    const IdentifierInfo *ModularImplFn,
                                     StringRef ImplName,
                                     ArrayRef<StringRef> Aspects) {
   return Existing->getModularImplFn() == ModularImplFn &&
@@ -7013,10 +7013,9 @@ static bool modularFormatAttrsEquiv(const 
ModularFormatAttr *Existing,
          llvm::equal(Existing->aspects(), Aspects);
 }
 
-ModularFormatAttr *
-Sema::mergeModularFormatAttr(Decl *D, const AttributeCommonInfo &CI,
-                             IdentifierInfo *ModularImplFn, StringRef ImplName,
-                             MutableArrayRef<StringRef> Aspects) {
+ModularFormatAttr *Sema::mergeModularFormatAttr(
+    Decl *D, const AttributeCommonInfo &CI, const IdentifierInfo 
*ModularImplFn,
+    StringRef ImplName, MutableArrayRef<StringRef> Aspects) {
   if (const auto *Existing = D->getAttr<ModularFormatAttr>()) {
     if (!modularFormatAttrsEquiv(Existing, ModularImplFn, ImplName, Aspects)) {
       Diag(Existing->getLocation(), diag::err_duplicate_attribute) << 
*Existing;

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

Reply via email to