[clang] a205695 - [Clang] Add template argument support for {con,de}structor attributes. (#151400)

via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 8 13:48:28 PDT 2025


Author: tynasello
Date: 2025-09-08T20:48:23Z
New Revision: a205695de4dce3c839dedbb78dd67e2a7758947a

URL: https://github.com/llvm/llvm-project/commit/a205695de4dce3c839dedbb78dd67e2a7758947a
DIFF: https://github.com/llvm/llvm-project/commit/a205695de4dce3c839dedbb78dd67e2a7758947a.diff

LOG: [Clang] Add template argument support for {con,de}structor attributes. (#151400)

Fixes: https://github.com/llvm/llvm-project/issues/67154

{Con, De}structor attributes in Clang only work with integer priorities
(inconsistent with GCC). This commit adds support to these attributes
for template arguments.

Built off of contributions from [abrachet](https://github.com/abrachet)
in [#67376](https://github.com/llvm/llvm-project/pull/67376).

Added: 
    clang/test/CodeGenCXX/destructor-attr.cpp
    clang/test/Sema/constructor-attribute.cpp

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/lib/CodeGen/CodeGenModule.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
    clang/test/AST/ast-dump-attr.cpp
    clang/test/CodeGenCXX/constructor-attr.cpp

Removed: 
    clang/test/Sema/constructor-attribute.c


################################################################################
diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0fb0e7178404e..e672d857b9a97 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1454,9 +1454,13 @@ def ConstInit : InheritableAttr {
 
 def Constructor : InheritableAttr {
   let Spellings = [GCC<"constructor">];
-  let Args = [DefaultIntArgument<"Priority", 65535>];
+  let Args = [ExprArgument<"Priority", 1>];
   let Subjects = SubjectList<[Function]>;
+  let TemplateDependent = 1;
   let Documentation = [CtorDtorDocs];
+  let AdditionalMembers = [{
+    static constexpr unsigned DefaultPriority = 65535; 
+  }];
 }
 
 def CPUSpecific : InheritableAttr {
@@ -1795,9 +1799,13 @@ def Deprecated : InheritableAttr {
 
 def Destructor : InheritableAttr {
   let Spellings = [GCC<"destructor">];
-  let Args = [DefaultIntArgument<"Priority", 65535>];
+  let Args = [ExprArgument<"Priority", 1>];
   let Subjects = SubjectList<[Function]>;
+  let TemplateDependent = 1;
   let Documentation = [CtorDtorDocs];
+  let AdditionalMembers = [{
+    static constexpr unsigned int DefaultPriority = 65535; 
+  }];
 }
 
 def EmptyBases : InheritableAttr, TargetSpecificAttr<TargetMicrosoftRecordLayout> {

diff  --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index c0cfc24f02877..87d2cd4ce468d 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -6376,10 +6376,18 @@ void CodeGenModule::EmitGlobalFunctionDefinition(GlobalDecl GD,
 
   SetLLVMFunctionAttributesForDefinition(D, Fn);
 
+  auto GetPriority = [this](const auto *Attr) -> int {
+    Expr *E = Attr->getPriority();
+    if (E) {
+      return E->EvaluateKnownConstInt(this->getContext()).getExtValue();
+    }
+    return Attr->DefaultPriority;
+  };
+
   if (const ConstructorAttr *CA = D->getAttr<ConstructorAttr>())
-    AddGlobalCtor(Fn, CA->getPriority());
+    AddGlobalCtor(Fn, GetPriority(CA));
   if (const DestructorAttr *DA = D->getAttr<DestructorAttr>())
-    AddGlobalDtor(Fn, DA->getPriority(), true);
+    AddGlobalDtor(Fn, GetPriority(DA), true);
   if (getLangOpts().OpenMP && D->hasAttr<OMPDeclareTargetDeclAttr>())
     getOpenMPRuntime().emitDeclareTargetFunction(D, GV);
 }

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 3ded60cd8b073..cb2c132cca978 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "clang/AST/APValue.h"
 #include "clang/AST/ASTConsumer.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTMutationListener.h"
@@ -58,6 +59,7 @@
 #include "clang/Sema/SemaSwift.h"
 #include "clang/Sema/SemaWasm.h"
 #include "clang/Sema/SemaX86.h"
+#include "llvm/ADT/APSInt.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Demangle/Demangle.h"
@@ -2156,29 +2158,51 @@ static void handleUnusedAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(::new (S.Context) UnusedAttr(S.Context, AL));
 }
 
+static ExprResult sharedGetConstructorDestructorAttrExpr(Sema &S,
+                                                         const ParsedAttr &AL) {
+  // If no Expr node exists on the attribute, return a nullptr result (default
+  // priority to be used). If Expr node exists but is not valid, return an
+  // invalid result. Otherwise, return the Expr.
+  Expr *E = nullptr;
+  if (AL.getNumArgs() == 1) {
+    E = AL.getArgAsExpr(0);
+    if (E->isValueDependent()) {
+      if (!E->isTypeDependent() && !E->getType()->isIntegerType()) {
+        S.Diag(AL.getLoc(), diag::err_attribute_argument_type)
+            << AL << AANT_ArgumentIntegerConstant << E->getSourceRange();
+        return ExprError();
+      }
+    } else {
+      uint32_t priority;
+      if (!S.checkUInt32Argument(AL, AL.getArgAsExpr(0), priority)) {
+        return ExprError();
+      }
+      return ConstantExpr::Create(S.Context, E,
+                                  APValue(llvm::APSInt::getUnsigned(priority)));
+    }
+  }
+  return E;
+}
+
 static void handleConstructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
-  uint32_t priority = ConstructorAttr::DefaultPriority;
   if (S.getLangOpts().HLSL && AL.getNumArgs()) {
     S.Diag(AL.getLoc(), diag::err_hlsl_init_priority_unsupported);
     return;
   }
-  if (AL.getNumArgs() &&
-      !S.checkUInt32Argument(AL, AL.getArgAsExpr(0), priority))
+  ExprResult E = sharedGetConstructorDestructorAttrExpr(S, AL);
+  if (E.isInvalid())
     return;
   S.Diag(D->getLocation(), diag::warn_global_constructor)
       << D->getSourceRange();
-
-  D->addAttr(::new (S.Context) ConstructorAttr(S.Context, AL, priority));
+  D->addAttr(ConstructorAttr::Create(S.Context, E.get(), AL));
 }
 
 static void handleDestructorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
-  uint32_t priority = DestructorAttr::DefaultPriority;
-  if (AL.getNumArgs() &&
-      !S.checkUInt32Argument(AL, AL.getArgAsExpr(0), priority))
+  ExprResult E = sharedGetConstructorDestructorAttrExpr(S, AL);
+  if (E.isInvalid())
     return;
   S.Diag(D->getLocation(), diag::warn_global_destructor) << D->getSourceRange();
-
-  D->addAttr(::new (S.Context) DestructorAttr(S.Context, AL, priority));
+  D->addAttr(DestructorAttr::Create(S.Context, E.get(), AL));
 }
 
 template <typename AttrTy>

diff  --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 910f99d3eb160..b3cbd7f8c1efe 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -234,6 +234,32 @@ static void instantiateDependentAnnotationAttr(
   }
 }
 
+template <typename Attr>
+static void sharedInstantiateConstructorDestructorAttr(
+    Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, const Attr *A,
+    Decl *New, ASTContext &C) {
+  Expr *tempInstPriority = nullptr;
+  {
+    EnterExpressionEvaluationContext Unevaluated(
+        S, Sema::ExpressionEvaluationContext::Unevaluated);
+    ExprResult Result = S.SubstExpr(A->getPriority(), TemplateArgs);
+    if (Result.isInvalid())
+      return;
+    tempInstPriority = Result.get();
+    if (std::optional<llvm::APSInt> CE =
+            tempInstPriority->getIntegerConstantExpr(C)) {
+      // Consistent with non-templated priority arguments, which must fit in a
+      // 32-bit unsigned integer.
+      if (!CE->isIntN(32)) {
+        S.Diag(tempInstPriority->getExprLoc(), diag::err_ice_too_large)
+            << toString(*CE, 10, false) << /*Size=*/32 << /*Unsigned=*/1;
+        return;
+      }
+    }
+  }
+  New->addAttr(Attr::Create(C, tempInstPriority, *A));
+}
+
 static Expr *instantiateDependentFunctionAttrCondition(
     Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs,
     const Attr *A, Expr *OldCond, const Decl *Tmpl, FunctionDecl *New) {
@@ -825,6 +851,18 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs,
       continue;
     }
 
+    if (auto *Constructor = dyn_cast<ConstructorAttr>(TmplAttr)) {
+      sharedInstantiateConstructorDestructorAttr(*this, TemplateArgs,
+                                                 Constructor, New, Context);
+      continue;
+    }
+
+    if (auto *Destructor = dyn_cast<DestructorAttr>(TmplAttr)) {
+      sharedInstantiateConstructorDestructorAttr(*this, TemplateArgs,
+                                                 Destructor, New, Context);
+      continue;
+    }
+
     if (const auto *EnableIf = dyn_cast<EnableIfAttr>(TmplAttr)) {
       instantiateDependentEnableIfAttr(*this, TemplateArgs, EnableIf, Tmpl,
                                        cast<FunctionDecl>(New));

diff  --git a/clang/test/AST/ast-dump-attr.cpp b/clang/test/AST/ast-dump-attr.cpp
index f5a7481571421..e66990068cb7a 100644
--- a/clang/test/AST/ast-dump-attr.cpp
+++ b/clang/test/AST/ast-dump-attr.cpp
@@ -88,7 +88,10 @@ __attribute__((pointer_with_type_tag(unsigned1,1,2)));
 
 void TestInt(void) __attribute__((constructor(123)));
 // CHECK:      FunctionDecl{{.*}}TestInt
-// CHECK-NEXT:   ConstructorAttr{{.*}} 123
+// CHECK-NEXT:   ConstructorAttr
+// CHECK-NEXT:   ConstantExpr
+// CHECK-NEXT:   value: Int 123
+// CHECK-NEXT:   IntegerLiteral{{.*}} 123
 
 static int TestString __attribute__((alias("alias1")));
 // CHECK:      VarDecl{{.*}}TestString

diff  --git a/clang/test/CodeGenCXX/constructor-attr.cpp b/clang/test/CodeGenCXX/constructor-attr.cpp
index ec27ed210ce76..d73b661f1ec40 100644
--- a/clang/test/CodeGenCXX/constructor-attr.cpp
+++ b/clang/test/CodeGenCXX/constructor-attr.cpp
@@ -1,6 +1,11 @@
 // RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -o - %s | FileCheck %s
 
 // CHECK: @llvm.global_ctors
+// CHECK-SAME: i32 65535, ptr @_ZN3Foo3fooEv
+// CHECK-SAME: i32 101, ptr @_Z22template_dependent_cxxILi101EEvv
+// CHECK-SAME: i32 102, ptr @_Z22template_dependent_gnuILi102EEvv
+// CHECK-SAME: i32 103, ptr @_Z1fv
+// CHECK-SAME: i32 104, ptr @_Z23template_dependent_nttpIiLi104EEvv
 
 // PR6521
 void bar();
@@ -10,3 +15,16 @@ struct Foo {
     bar();
   }
 };
+
+
+template <int P>
+[[gnu::constructor(P)]] void template_dependent_cxx() {}
+template <int P>
+__attribute__((constructor(P))) void template_dependent_gnu() {}
+template <typename T, int P = sizeof(T) * 26>
+[[gnu::constructor(P)]] void template_dependent_nttp() {}
+
+template void template_dependent_cxx<101>();
+template void template_dependent_gnu<102>();
+[[gnu::constructor(103)]] void f() {}
+template void template_dependent_nttp<int>();

diff  --git a/clang/test/CodeGenCXX/destructor-attr.cpp b/clang/test/CodeGenCXX/destructor-attr.cpp
new file mode 100644
index 0000000000000..f0500caed3ccd
--- /dev/null
+++ b/clang/test/CodeGenCXX/destructor-attr.cpp
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -o - %s | FileCheck %s
+
+// CHECK: @llvm.global_dtors
+// CHECK-SAME: i32 65535, ptr @_ZN3Foo3fooEv
+// CHECK-SAME: i32 101, ptr @_Z22template_dependent_cxxILi101EEvv
+// CHECK-SAME: i32 104, ptr @_Z23template_dependent_nttpIiLi104EEvv
+
+// PR6521
+void bar();
+struct Foo {
+  // CHECK-LABEL: define linkonce_odr {{.*}}void @_ZN3Foo3fooEv
+  static void foo() __attribute__((destructor)) {
+    bar();
+  }
+};
+
+template <int P>
+[[gnu::destructor(P)]] void template_dependent_cxx() {}
+template <typename T, int P = sizeof(T) * 26>
+[[gnu::destructor(P)]] void template_dependent_nttp() {}
+
+template void template_dependent_cxx<101>();
+template void template_dependent_nttp<int>();

diff  --git a/clang/test/Sema/constructor-attribute.c b/clang/test/Sema/constructor-attribute.c
deleted file mode 100644
index 2317c7735bda5..0000000000000
--- a/clang/test/Sema/constructor-attribute.c
+++ /dev/null
@@ -1,16 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -verify -Wno-strict-prototypes %s
-
-int x __attribute__((constructor)); // expected-warning {{'constructor' attribute only applies to functions}}
-int f(void) __attribute__((constructor));
-int f(void) __attribute__((constructor(1)));
-int f(void) __attribute__((constructor(1,2))); // expected-error {{'constructor' attribute takes no more than 1 argument}}
-int f(void) __attribute__((constructor(1.0))); // expected-error {{'constructor' attribute requires an integer constant}}
-int f(void) __attribute__((constructor(0x100000000))); // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}}
-
-int x __attribute__((destructor)); // expected-warning {{'destructor' attribute only applies to functions}}
-int f(void) __attribute__((destructor));
-int f(void) __attribute__((destructor(1)));
-int f(void) __attribute__((destructor(1,2))); // expected-error {{'destructor' attribute takes no more than 1 argument}}
-int f(void) __attribute__((destructor(1.0))); // expected-error {{'destructor' attribute requires an integer constant}}
-
-void knr() __attribute__((constructor));

diff  --git a/clang/test/Sema/constructor-attribute.cpp b/clang/test/Sema/constructor-attribute.cpp
new file mode 100644
index 0000000000000..81e9cb3141f1e
--- /dev/null
+++ b/clang/test/Sema/constructor-attribute.cpp
@@ -0,0 +1,74 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify -Wno-strict-prototypes %s
+// RUN: %clang_cc1 -x c -fsyntax-only -verify -Wno-strict-prototypes %s
+
+int x __attribute__((constructor)); // expected-warning {{'constructor' attribute only applies to functions}}
+int f(void) __attribute__((constructor));
+int f(void) __attribute__((constructor(1)));
+int f(void) __attribute__((constructor(1,2))); // expected-error {{'constructor' attribute takes no more than 1 argument}}
+int f(void) __attribute__((constructor(1.0))); // expected-error {{'constructor' attribute requires an integer constant}}
+int f(void) __attribute__((constructor(0x100000000))); // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}}
+void knr() __attribute__((constructor));
+
+#ifdef __cplusplus
+template <float P> [[gnu::constructor(P)]] void f(); // expected-error {{'gnu::constructor' attribute requires an integer constant}}
+template <double P> [[gnu::constructor(P)]] void f(); // expected-error {{'gnu::constructor' attribute requires an integer constant}}
+template <int *P> [[gnu::constructor(P)]] void f(); // expected-error {{'gnu::constructor' attribute requires an integer constant}}
+
+template <long long P> [[gnu::constructor(P)]] void f() {} // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} 
+template void f<1LL<<32>(); // expected-note {{in instantiation of function template specialization 'f<4294967296LL>' requested here}}
+template void f<101>();
+
+template <typename T> [[gnu::constructor(static_cast<T>(1LL<<32))]] void f() {} // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} 
+template void f<long long>(); // expected-note {{in instantiation of function template specialization 'f<long long>' requested here}}
+template void f<int>();
+
+template <typename T>
+[[gnu::constructor(static_cast<T>(101))]] void g() {}
+template void g<int>();
+template void g<long long>();
+
+template <typename T>
+[[gnu::constructor(static_cast<T>(T{101}))]] void h() {}
+template void h<int>();
+template void h<long long>();
+
+template <typename T>
+[[gnu::constructor(static_cast<T>(sizeof(T[101])))]] void a() {}
+template void a<int>();
+template void a<long long>();
+#endif
+
+int yd __attribute__((destructor)); // expected-warning {{'destructor' attribute only applies to functions}}
+int fd(void) __attribute__((destructor));
+int fd(void) __attribute__((destructor(1)));
+int fd(void) __attribute__((destructor(1,2))); // expected-error {{'destructor' attribute takes no more than 1 argument}}
+int fd(void) __attribute__((destructor(1.0))); // expected-error {{'destructor' attribute requires an integer constant}}
+
+#ifdef __cplusplus
+template <float P> [[gnu::destructor(P)]] void fd(); // expected-error {{'gnu::destructor' attribute requires an integer constant}}
+template <double P> [[gnu::destructor(P)]] void fd(); // expected-error {{'gnu::destructor' attribute requires an integer constant}}
+template <int *P> [[gnu::destructor(P)]] void fd(); // expected-error {{'gnu::destructor' attribute requires an integer constant}}
+
+template <long long P> [[gnu::destructor(P)]] void fd() {} // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} 
+template void fd<1LL<<32>(); // expected-note {{in instantiation of function template specialization 'fd<4294967296LL>' requested here}}
+template void fd<101>();
+
+template <typename T> [[gnu::destructor(static_cast<T>(1LL<<32))]] void fd() {} // expected-error {{integer constant expression evaluates to value 4294967296 that cannot be represented in a 32-bit unsigned integer type}} 
+template void fd<long long>(); // expected-note {{in instantiation of function template specialization 'fd<long long>' requested here}}
+template void fd<int>();
+
+template <typename T>
+[[gnu::destructor(static_cast<T>(101))]] void gd() {}
+template void gd<int>();
+template void gd<long long>();
+
+template <typename T>
+[[gnu::destructor(static_cast<T>(T{101}))]] void hd() {}
+template void hd<int>();
+template void hd<long long>();
+
+template <typename T>
+[[gnu::destructor(static_cast<T>(sizeof(T[101])))]] void ad() {}
+template void ad<int>();
+template void ad<long long>();
+#endif


        


More information about the cfe-commits mailing list