[clang] [llvm] [clang][CodeGen] Place constructor/destructor functions in .text.startup/.text… (PR #200221)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 24 08:41:13 PDT 2026
https://github.com/Jiang-XueZhi updated https://github.com/llvm/llvm-project/pull/200221
>From a97e45914135eec52583509c0c3573d90fa2f965 Mon Sep 17 00:00:00 2001
From: Jiang-XueZhi <17853142875 at 163.com>
Date: Fri, 29 May 2026 00:34:30 +0800
Subject: [PATCH] [clang][CodeGen] Place constructor/destructor functions in
.text.startup/.text.exit
Emit `__attribute__((constructor))` functions into `.text.startup` and
`__attribute__((destructor))` functions into `.text.exit` on ELF targets,
matching GCC's behavior.
GCC groups constructor functions in `.text.startup` and destructor functions
in `.text.exit`. This improves memory utilization at runtime:
- Startup-only code is aggregated into a contiguous region, allowing the
operating system to load it as a unit during initialization and
subsequently reclaim those pages.
- Exit-only code is similarly isolated, preventing it from polluting the
hot code path.
- ibache utilization is improved because code that runs only once (at
startup or exit) does not displace frequently-executed code from the
instruction cache.
Clang currently places both constructor and destructor attribute functions
in the generic `.text` section alongside all other code, losing this
optimization opportunity entirely.
In systems with many constructor attribute functions, this fragmentation
causes measurable memory pressure: constructor bodies scattered throughout
`.text` remain resident long after initialization completes, increasing
the resident set size and the risk of future page faults.
Co-Authored-By: deepseek-v4-pro <deepseek.com>
---
clang/lib/CodeGen/CodeGenModule.cpp | 20 +++++++
.../test/CodeGen/constructor-section-prefix.c | 29 +++++++++
llvm/lib/CodeGen/TargetInstrInfo.cpp | 3 +-
.../AArch64/startup-exit-section-prefix.ll | 31 ++++++++++
.../Generic/machine-function-splitter.ll | 60 +++++++++++++++++++
5 files changed, 142 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CodeGen/constructor-section-prefix.c
create mode 100644 llvm/test/CodeGen/AArch64/startup-exit-section-prefix.ll
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 817b2b5e6a69d..a690fa029940e 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -3383,6 +3383,18 @@ bool CodeGenModule::GetCPUAndFeaturesAttributes(GlobalDecl GD,
return AddedAttr;
}
+/// Return the ELF section prefix for a function based on the function's
+/// code-region affinity: "startup" for functions that run exclusively at
+/// program startup, "exit" for functions that run exclusively at program
+/// exit. Returns nullptr when the function has no such affinity.
+static const char *getCodeRegionSectionPrefix(const FunctionDecl *D) {
+ if (D->hasAttr<ConstructorAttr>())
+ return "startup";
+ if (D->hasAttr<DestructorAttr>())
+ return "exit";
+ return nullptr;
+}
+
void CodeGenModule::setNonAliasAttributes(GlobalDecl GD,
llvm::GlobalObject *GO) {
const Decl *D = GD.getDecl();
@@ -3428,6 +3440,14 @@ void CodeGenModule::setNonAliasAttributes(GlobalDecl GD,
GO->setSection(CSA->getName());
else if (const auto *SA = D->getAttr<SectionAttr>())
GO->setSection(SA->getName());
+
+ // Emit constructor/destructor functions into .text.startup/.text.exit on
+ // ELF targets, matching GCC behavior. Skip when the user already specified
+ // an explicit section.
+ if (getTarget().getTriple().isOSBinFormatELF() && !GO->hasSection())
+ if (const auto *FD = dyn_cast<FunctionDecl>(D))
+ if (const char *Prefix = getCodeRegionSectionPrefix(FD))
+ GO->setSectionPrefix(Prefix);
}
getTargetCodeGenInfo().setTargetAttributes(D, GO, *this);
diff --git a/clang/test/CodeGen/constructor-section-prefix.c b/clang/test/CodeGen/constructor-section-prefix.c
new file mode 100644
index 0000000000000..d52ce19e9c0ad
--- /dev/null
+++ b/clang/test/CodeGen/constructor-section-prefix.c
@@ -0,0 +1,29 @@
+// REQUIRES: aarch64-registered-target
+
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm %s -o - | FileCheck --check-prefix=CHECK-IR %s
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -S -ffunction-sections %s -o - | FileCheck --check-prefix=CHECK-ASM %s
+// RUN: %clang_cc1 -triple aarch64-linux-gnu -emit-llvm %s -o - | FileCheck --check-prefix=CHECK-EXPLICIT %s --implicit-check-not='@ctor_explicit_section(){{.*}}section_prefix'
+
+// CHECK-IR: define{{.*}} void @plain_ctor(){{.*}} !section_prefix ![[CTOR:[0-9]+]]
+// CHECK-IR: define{{.*}} void @plain_dtor(){{.*}} !section_prefix ![[DTOR:[0-9]+]]
+// CHECK-IR: define{{.*}} void @ctor_prio(){{.*}} !section_prefix ![[CTOR]]
+// CHECK-IR: define{{.*}} void @dtor_prio(){{.*}} !section_prefix ![[DTOR]]
+// CHECK-IR: ![[CTOR]] = !{!"section_prefix", !"startup"}
+// CHECK-IR: ![[DTOR]] = !{!"section_prefix", !"exit"}
+
+// CHECK-ASM: .section .text.startup.plain_ctor,"ax", at progbits
+// CHECK-ASM: .section .text.exit.plain_dtor,"ax", at progbits
+// CHECK-ASM: .section .text.startup.ctor_prio,"ax", at progbits
+// CHECK-ASM: .section .text.exit.dtor_prio,"ax", at progbits
+
+// CHECK-EXPLICIT: define{{.*}} void @ctor_explicit_section(){{.*}} section ".my_section"
+
+void __attribute__((constructor)) plain_ctor(void) {}
+
+void __attribute__((destructor)) plain_dtor(void) {}
+
+void __attribute__((constructor(101))) ctor_prio(void) {}
+
+void __attribute__((destructor(202))) dtor_prio(void) {}
+
+void __attribute__((constructor, section(".my_section"))) ctor_explicit_section(void) {}
diff --git a/llvm/lib/CodeGen/TargetInstrInfo.cpp b/llvm/lib/CodeGen/TargetInstrInfo.cpp
index 92fc628e888e5..b34aaa6eacc94 100644
--- a/llvm/lib/CodeGen/TargetInstrInfo.cpp
+++ b/llvm/lib/CodeGen/TargetInstrInfo.cpp
@@ -1865,7 +1865,8 @@ bool TargetInstrInfo::isFunctionSafeToSplit(const MachineFunction &MF) const {
// or functions of unknown hotness. Lukewarm functions have no prefix.
std::optional<StringRef> SectionPrefix = MF.getFunction().getSectionPrefix();
if (SectionPrefix &&
- (*SectionPrefix == "unlikely" || *SectionPrefix == "unknown")) {
+ (*SectionPrefix == "unlikely" || *SectionPrefix == "unknown" ||
+ *SectionPrefix == "startup" || *SectionPrefix == "exit")) {
return false;
}
diff --git a/llvm/test/CodeGen/AArch64/startup-exit-section-prefix.ll b/llvm/test/CodeGen/AArch64/startup-exit-section-prefix.ll
new file mode 100644
index 0000000000000..f67b40287c78f
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/startup-exit-section-prefix.ll
@@ -0,0 +1,31 @@
+; RUN: llc -mtriple aarch64-linux-gnu -o - %s | FileCheck %s --check-prefix=PLAIN
+; RUN: llc -mtriple aarch64-linux-gnu -function-sections -o - %s | FileCheck %s --check-prefix=FUNC-SECT
+
+;; A constructor function receives section_prefix "startup".
+; PLAIN: .section .text.startup.,"ax", at progbits
+; FUNC-SECT: .section .text.startup.my_ctor,"ax", at progbits
+define void @my_ctor() !section_prefix !0 {
+ ret void
+}
+
+;; A destructor function receives section_prefix "exit".
+; PLAIN: .section .text.exit.,"ax", at progbits
+; FUNC-SECT: .section .text.exit.my_dtor,"ax", at progbits
+define void @my_dtor() !section_prefix !1 {
+ ret void
+}
+
+; PLAIN: .section .text.startup.,"ax", at progbits
+; FUNC-SECT: .section .text.startup.startup,"ax", at progbits
+define void @startup() !section_prefix !0 {
+ ret void
+}
+
+; PLAIN: .section .text.exit.,"ax", at progbits
+; FUNC-SECT: .section .text.exit.exit,"ax", at progbits
+define void @exit() !section_prefix !1 {
+ ret void
+}
+
+!0 = !{!"section_prefix", !"startup"}
+!1 = !{!"section_prefix", !"exit"}
diff --git a/llvm/test/CodeGen/Generic/machine-function-splitter.ll b/llvm/test/CodeGen/Generic/machine-function-splitter.ll
index d798b2875645b..c6e6731a98ea0 100644
--- a/llvm/test/CodeGen/Generic/machine-function-splitter.ll
+++ b/llvm/test/CodeGen/Generic/machine-function-splitter.ll
@@ -704,6 +704,62 @@ lpad:
resume { ptr, i32 } %2
}
+define void @foo24(i1 zeroext %0) nounwind !prof !29 {
+; MFS-DEFAULTS-LABEL: foo24
+; MFS-DEFAULTS: foo24.cold:
+ br i1 %0, label %2, label %4, !prof !31
+
+2: ; preds = %1
+ %3 = call i32 @bar()
+ br label %6
+
+4: ; preds = %1
+ %5 = call i32 @baz()
+ br label %6
+
+6: ; preds = %4, %2
+ %7 = tail call i32 @qux()
+ ret void
+}
+
+define void @foo25(i1 zeroext %0) nounwind !prof !29 !section_prefix !30 {
+; MFS-DEFAULTS: .section .text.startup.
+; MFS-DEFAULTS-LABEL: foo25
+; MFS-DEFAULTS-NOT: foo25.cold:
+ br i1 %0, label %2, label %4, !prof !31
+
+2: ; preds = %1
+ %3 = call i32 @bar()
+ br label %6
+
+4: ; preds = %1
+ %5 = call i32 @baz()
+ br label %6
+
+6: ; preds = %4, %2
+ %7 = tail call i32 @qux()
+ ret void
+}
+
+define void @foo26(i1 zeroext %0) nounwind !prof !29 !section_prefix !32 {
+; MFS-DEFAULTS: .section .text.exit.
+; MFS-DEFAULTS-LABEL: foo26
+; MFS-DEFAULTS-NOT: foo26.cold:
+ br i1 %0, label %2, label %4, !prof !31
+
+2: ; preds = %1
+ %3 = call i32 @bar()
+ br label %6
+
+4: ; preds = %1
+ %5 = call i32 @baz()
+ br label %6
+
+6: ; preds = %4, %2
+ %7 = tail call i32 @qux()
+ ret void
+}
+
declare i32 @bar()
declare i32 @baz()
declare i32 @bam()
@@ -743,3 +799,7 @@ declare i32 @__gxx_personality_v0(...)
!26 = !{!"branch_weights", i32 1000, i32 6000}
!27 = !{!"function_entry_count", i64 10000}
!28 = !{!"branch_weights", i32 0, i32 4000, i32 4000, i32 0, i32 0}
+!29 = !{!"function_entry_count", i64 50}
+!30 = !{!"section_prefix", !"startup"}
+!31 = !{!"branch_weights", i32 50, i32 0}
+!32 = !{!"section_prefix", !"exit"}
More information about the cfe-commits
mailing list