[clang] [llvm] [Clang][AIX] Add -mloadtime-comment-vars flag to preserve identifying variables (PR #187986)
Tony Varghese via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 17 07:22:56 PDT 2026
https://github.com/tonykuttai updated https://github.com/llvm/llvm-project/pull/187986
>From c4727d44fd6f3c2ef37c1afd2e08005ab9b4b13b Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 19 Jun 2026 15:46:49 +0530
Subject: [PATCH 1/7] [Clang][AIX] Add -mloadtime-comment-vars support to
preserve variables in the final object file.
---
clang/docs/LanguageExtensions.md | 65 ++++++++++
clang/include/clang/Basic/CodeGenOptions.h | 3 +
clang/include/clang/Options/Options.td | 7 ++
clang/lib/CodeGen/CodeGenModule.cpp | 119 ++++++++++++++++++
clang/lib/CodeGen/CodeGenModule.h | 18 +++
clang/lib/Driver/ToolChains/Clang.cpp | 9 ++
.../CodeGen/PowerPC/loadtime-comment-mixed.c | 12 ++
clang/test/CodeGen/loadtime-comment-vars.c | 61 +++++++++
clang/test/Driver/mloadtime-comment-vars.c | 11 ++
.../lower-comment-string.ll | 21 ++--
10 files changed, 318 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-mixed.c
create mode 100644 clang/test/CodeGen/loadtime-comment-vars.c
create mode 100644 clang/test/Driver/mloadtime-comment-vars.c
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index f5313e0378ca0..410647270f2f5 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6510,6 +6510,71 @@ When `#pragma comment(copyright, ...)` appears in a C++20 module interface
unit, the copyright string is embedded only in the object file compiled from
that interface unit. Importing TUs do not re-emit the string.
+### Preserving Identifying Variables with -mloadtime-comment-vars
+
+The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
+global variable names that should be preserved in the final object file as
+loadtime identifying strings. This is an AIX-specific feature and is ignored
+on other targets.
+
+This flag complements `#pragma comment(copyright, ...)` for codebases that
+already use the traditional UNIX convention of embedding identifying strings
+directly in source variables rather than via a pragma.
+
+Syntax:
+
+```console
+-mloadtime-comment-vars=<var1>[,<var2>,...]
+```
+
+Valid variable types:
+
+A variable named in the list must meet both of these conditions to be
+preserved:
+
+- Its type must be a character pointer (`char *`, `const char *`) or a
+ character array (`char[]`).
+- It must have an initializer.
+
+Variables that fail either check -- for example, an `int` or a `struct` --
+are silently skipped. Variables that appear in the list but are not defined in
+the translation unit are also ignored.
+
+Example:
+
+```c
+static char *sccsid = "@(#) MyApp Version 1.0";
+static char version[] = "@(#) Built 2026-05-24";
+
+void foo() {}
+```
+
+Compiled with:
+
+```console
+clang -target powerpc64-ibm-aix \
+ -mloadtime-comment-vars=sccsid,version \
+ -c source.c -o source.o
+```
+
+Both `sccsid` and `version` survive optimization and are retained in the
+object file.
+
+```console
+$ what source.o
+source.o:
+ MyApp Version 1.0
+ Built 2026-05-24
+```
+
+Interaction with `#pragma comment(copyright, ...)`:
+
+The two mechanisms can be used together in the same translation unit. The
+pragma produces a dedicated `__loadtime_comment_str` symbol placed in the
+`__loadtime_comment` section, while `-mloadtime-comment-vars` preserves
+the named source variables in place using `.ref` directives. Both sets of
+strings appear in the final object file independently.
+
## Evaluating Object Size
Clang supports the builtins `__builtin_object_size` and
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index c12434135a198..768882d346c9b 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -355,6 +355,9 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// A list of linker options to embed in the object file.
std::vector<std::string> LinkerOptions;
+ /// List of global variable names to preserve as loadtime comment variables.
+ std::vector<std::string> LoadTimeCommentVars;
+
/// Name of the profile file to use as output for -fprofile-instr-generate,
/// -fprofile-generate, and -fcs-profile-generate.
std::string InstrProfileOutput;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index bad443efb6c65..a24fbfc50960a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4841,6 +4841,13 @@ def fvisibility_global_new_delete_EQ : Joined<["-"], "fvisibility-global-new-del
Visibility<[ClangOption, CC1Option]>,
HelpText<"The visibility for global C++ operator new and delete declarations. If 'source' is specified the visibility is not adjusted">,
MarshallingInfoVisibilityGlobalNewDelete<LangOpts<"GlobalAllocationFunctionVisibility">, "ForceDefault">;
+def mloadtime_comment_vars_EQ
+ : CommaJoined<["-"], "mloadtime-comment-vars=">,
+ Group<m_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Comma-separated list of global variable names to treat as "
+ "loadtime variables">,
+ MarshallingInfoStringVector<CodeGenOpts<"LoadTimeCommentVars">>;
def mdefault_visibility_export_mapping_EQ : Joined<["-"], "mdefault-visibility-export-mapping=">,
Values<"none,explicit,all">,
NormalizedValuesScope<"LangOptions::DefaultVisiblityExportMapping">,
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 78627047b19ad..a20d54b3cf16d 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1132,6 +1132,13 @@ void CodeGenModule::Release() {
Module *Primary = getContext().getCurrentNamedModule();
if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
EmitModuleInitializers(Primary);
+
+ // Queue loadtime comment variable candidates into the deferred emission
+ // list before EmitDeferred() runs, so their initializers (which may
+ // reference other globals, e.g. static const char *p = a;) are emitted
+ // through the normal infrastructure with correct ordering.
+ QueueLoadTimeCommentVarEmission();
+
EmitDeferred();
DeferredDecls.insert_range(EmittedDeferredDecls);
EmittedDeferredDecls.clear();
@@ -1826,6 +1833,9 @@ void CodeGenModule::Release() {
EmitBackendOptionsMetadata(getCodeGenOpts());
+ // Mark loadtime comment variables specified via -mloadtime-comment-vars.
+ ProcessLoadTimeCommentVars();
+
// If there is device offloading code embed it in the host now.
EmbedObject(&getModule(), CodeGenOpts, *getFileSystem(), getDiags());
@@ -4389,6 +4399,115 @@ bool CodeGenModule::MayBeEmittedEagerly(const ValueDecl *Global) {
return true;
}
+/// Check if a variable declaration is suitable to be treated as a loadtime
+/// comment variable. Valid variables must be character pointers or character
+/// arrays with an initializer.
+bool CodeGenModule::isValidLoadTimeCommentVariable(const VarDecl *D) const {
+ // Must be a valid declaration and must have an initializer (the string).
+ if (!D || !D->hasInit())
+ return false;
+
+ QualType Ty = D->getType();
+
+ // 1. Handle Pointers (e.g., char *sccsid, const char *copyright).
+ if (const PointerType *PT = Ty->getAs<PointerType>()) {
+ if (PT->getPointeeType()->isAnyCharacterType())
+ return true;
+ }
+
+ // 2. Handle Arrays (e.g., char version[])
+ if (const ArrayType *AT = getContext().getAsArrayType(Ty)) {
+ if (AT->getElementType()->isAnyCharacterType())
+ return true;
+ }
+
+ return false; // Reject ints, structs, etc.
+}
+
+/// Check if a variable is eligible to be treated as a loadtime comment
+/// variable. This requires: (1) the variable name is in the requested list
+/// and (2) the variable type is valid (char pointer or array with initializer).
+bool CodeGenModule::isLoadTimeCommentCandidateVariable(
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars) {
+ if (!llvm::is_contained(LoadTimeCommentVars, VD->getName()))
+ return false;
+ return isValidLoadTimeCommentVariable(VD);
+}
+
+/// QueueLoadTimeCommentVarEmission: Called before EmitDeferred().
+/// Move loadtime comment variable candidates from DeferredDecls into
+/// DeferredDeclsToEmit so that the normal deferred emission machinery
+/// defines them — including any globals their initializers reference
+/// (e.g. static const char *p = a;).
+void CodeGenModule::QueueLoadTimeCommentVarEmission() {
+ if (!getTriple().isOSAIX())
+ return;
+
+ const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
+ if (LoadTimeCommentVars.empty())
+ return;
+
+ TranslationUnitDecl *TU = getContext().getTranslationUnitDecl();
+ for (auto *D : TU->decls()) {
+ auto *VD = dyn_cast<VarDecl>(D);
+ if (!VD)
+ continue;
+ if (!isLoadTimeCommentCandidateVariable(VD, LoadTimeCommentVars))
+ continue;
+
+ // Move the decl from DeferredDecls -> DeferredDeclsToEmit so EmitDeferred
+ // will define it. If it is already being emitted (e.g. it is referenced
+ // somewhere), this is a harmless duplicate that EmitDeferred ignores.
+ GlobalDecl GD(VD);
+ StringRef MangledName = getMangledName(GD);
+ auto DDI = DeferredDecls.find(MangledName);
+ if (DDI != DeferredDecls.end()) {
+ addDeferredDeclToEmit(DDI->second);
+ DeferredDecls.erase(DDI);
+ }
+ }
+}
+
+/// ProcessLoadTimeCommentVars: Called after EmitDeferred().
+/// Attach loadtime_comment metadata and add each variable to
+/// llvm.compiler.used. By this point the deferred emission loop has already
+/// defined the globals, so we only need to look them up and annotate them. Only
+/// valid on AIX targets.
+void CodeGenModule::ProcessLoadTimeCommentVars() {
+ if (!getTriple().isOSAIX())
+ return;
+
+ const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
+ if (LoadTimeCommentVars.empty())
+ return;
+
+ auto &C = getLLVMContext();
+ TranslationUnitDecl *TU = getContext().getTranslationUnitDecl();
+
+ for (auto *D : TU->decls()) {
+ auto *VD = dyn_cast<VarDecl>(D);
+ if (!VD)
+ continue;
+ if (!isLoadTimeCommentCandidateVariable(VD, LoadTimeCommentVars))
+ continue;
+
+ // Look up the LLVM global that EmitDeferred() should have defined.
+ llvm::GlobalValue *GV = GetGlobalValue(getMangledName(GlobalDecl(VD)));
+ if (!GV || GV->isDeclaration())
+ continue;
+
+ auto *GVar = dyn_cast<llvm::GlobalVariable>(GV);
+ if (!GVar)
+ continue;
+
+ // Mark with loadtime_comment metadata for LowerCommentStringPass.
+ GVar->setMetadata("loadtime_comment", llvm::MDNode::get(C, {}));
+
+ // Prevent the optimizer from removing the global variable.
+ llvm::appendToCompilerUsed(getModule(), {GVar});
+ }
+}
+
ConstantAddress CodeGenModule::GetAddrOfMSGuidDecl(const MSGuidDecl *GD) {
StringRef Name = getMangledName(GD);
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 0abd75ccb0551..f6ea77860a9a2 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2197,6 +2197,24 @@ class CodeGenModule : public CodeGenTypeCache {
/// Emit deactivation symbols for any PFP fields whose offset is taken with
/// offsetof.
void emitPFPFieldsWithEvaluatedOffset();
+
+ /// Check if a variable declaration is suitable to be treated as a loadtime
+ /// comment variable (must be a character pointer or array with initializer).
+ bool isValidLoadTimeCommentVariable(const VarDecl *D) const;
+
+ /// Check if a variable is eligible to be treated as a loadtime comment
+ /// variable (must be in the requested list and have a valid char type).
+ bool isLoadTimeCommentCandidateVariable(
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
+
+ /// Queue loadtime comment variable candidates into the deferred
+ /// emission list before EmitDeferred() so their initializers are emitted
+ /// through the normal infrastructure with correct ordering.
+ void QueueLoadTimeCommentVarEmission();
+
+ /// Attach loadtime_comment metadata and add variables to
+ /// llvm.compiler.used after EmitDeferred() has defined them.
+ void ProcessLoadTimeCommentVars();
};
} // end namespace CodeGen
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index bdf72f848aeff..85631a2733fa3 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6353,6 +6353,15 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
else if (UnwindTables)
CmdArgs.push_back("-funwind-tables=1");
+ // Forward loadtime-comment vars option to cc1 only on AIX targets.
+ if (Arg *A = Args.getLastArg(options::OPT_mloadtime_comment_vars_EQ)) {
+ if (Triple.isOSAIX())
+ A->render(Args, CmdArgs);
+ else
+ D.Diag(diag::warn_drv_unsupported_option_for_target)
+ << A->getAsString(Args) << TripleStr;
+ }
+
// Sframe unwind tables are independent of the other types. Although also
// defined for aarch64, only x86_64 support is implemented at the moment.
if (Arg *A = Args.getLastArg(options::OPT_gsframe)) {
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-mixed.c b/clang/test/CodeGen/PowerPC/loadtime-comment-mixed.c
new file mode 100644
index 0000000000000..64c6ec66b6160
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-mixed.c
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -mloadtime-comment-vars=sccsid -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+#pragma comment(copyright, "@(#) pragma path")
+
+static char *sccsid = "@(#) option path";
+
+void f(void) {}
+
+// CHECK: @[[PRAGMA:__loadtime_comment_str_[0-9a-f]+]] = weak_odr hidden unnamed_addr constant [17 x i8] c"@(#) pragma path\00", section "__loadtime_comment", align 1, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK: @sccsid = internal global ptr @.str, align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK: @llvm.compiler.used = appending global [2 x ptr] [ptr @[[PRAGMA]], ptr @sccsid], section "llvm.metadata"
diff --git a/clang/test/CodeGen/loadtime-comment-vars.c b/clang/test/CodeGen/loadtime-comment-vars.c
new file mode 100644
index 0000000000000..d54f848ca2eea
--- /dev/null
+++ b/clang/test/CodeGen/loadtime-comment-vars.c
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+// RUN: %clang_cc1 -O2 -triple x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=NONAIX
+
+// 1. String pointer
+static char *sccsid = "@(#) sccsid Version 1.0";
+
+// 2. String array
+static char version[] = "@(#) Copyright Version 2.0";
+
+// 3. Const string (Not in CLI list, should NOT be emitted)
+static const char *copyright = "@(#) Copyright 2026";
+
+// 4. Integer (In CLI list but invalid type, should NOT be emitted)
+static int build_number = 12345;
+
+// 5. Struct (not in CLI list and invalid type, NOT emitted)
+struct build_info {
+ int major;
+ int minor;
+} static build_data = {1, 0};
+
+// 6. Deferred: pointer whose initializer references another static global.
+// Both the pointer AND the string it points to must be emitted.
+static const char dummy[] = "dummy copyright deferred";
+static const char *same_copyright = dummy;
+
+// 7. Variable already referenced (eager emission path)
+static char *active = "@(#) active string";
+void bar() { (void)active; }
+
+// 8. Variable listed but only declared (extern)
+extern char *not_defined_here;
+
+void foo() {}
+
+// CHECK-DAG: @active = internal global ptr @.str, align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK: @.str = private unnamed_addr constant [19 x i8] c"@(#) active string\00", align {{[0-9]+}}
+// CHECK-DAG: @sccsid = internal global ptr @.str.1, align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK: @.str.1 = private unnamed_addr constant [24 x i8] c"@(#) sccsid Version 1.0\00", align {{[0-9]+}}
+// CHECK-DAG: @version = internal global [27 x i8] c"@(#) Copyright Version 2.0\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @same_copyright = internal global ptr @dummy, align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK: @dummy = internal constant [25 x i8] c"dummy copyright deferred\00"
+// CHECK: @llvm.compiler.used = appending global [4 x ptr]
+// CHECK-SAME: ptr @sccsid
+// CHECK-SAME: ptr @version
+// CHECK-SAME: ptr @same_copyright
+// CHECK-SAME: ptr @active
+// CHECK-SAME: section "llvm.metadata"
+
+// Ensure unrequested/invalid variables are not emitted
+// CHECK-NOT: @copyright
+// CHECK-NOT: @build_number
+// CHECK-NOT: @build_data
+// CHECK-NOT: @not_defined_here
+
+// NONAIX-NOT: loadtime_comment
+// NONAIX-NOT: @sccsid
+// NONAIX-NOT: @version
+
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
new file mode 100644
index 0000000000000..a443c85aec1f7
--- /dev/null
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -0,0 +1,11 @@
+// RUN: %clang -### -target powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s
+// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s --check-prefix=NONAIX
+
+// CHECK: "-cc1"
+// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
+
+// NONAIX: warning: ignoring '-mloadtime-comment-vars=sccsid,version' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
+// NONAIX: "-cc1"
+// NONAIX-NOT: "-mloadtime-comment-vars=sccsid,version"
+
+int main(void) { return 0; }
diff --git a/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
index dcae2e3b99d26..ff09388f9c71b 100644
--- a/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
+++ b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
@@ -9,7 +9,9 @@
target triple = "powerpc-ibm-aix"
@__loadtime_comment_str_f20696a95b638f0b = weak_odr hidden unnamed_addr constant [24 x i8] c"@(#) Copyright TU1 v1.0\00", section "__loadtime_comment", align 1, !loadtime_comment !0
- at llvm.compiler.used = appending global [1 x ptr] [ptr @__loadtime_comment_str_f20696a95b638f0b], section "llvm.metadata"
+ at .loadtime_comment_vars.str = private unnamed_addr constant [22 x i8] c"loadtime_comment vars\00", align 1
+ at loadtime_comment_vars_gv = internal global ptr @.loadtime_comment_vars.str, align 8, !loadtime_comment !0
+ at llvm.compiler.used = appending global [2 x ptr] [ptr @__loadtime_comment_str_f20696a95b638f0b, ptr @loadtime_comment_vars_gv], section "llvm.metadata"
define void @f0() {
entry:
@@ -23,16 +25,19 @@ entry:
!0 = !{}
; ---- Globals --------------------------------------------
; CHECK: @[[LOADTIME_COMMENT_STR:__loadtime_comment_str_[0-9a-f]+]] = weak_odr hidden unnamed_addr constant [24 x i8] c"@(#) Copyright TU1 v1.0\00", section "__loadtime_comment", align 1, !loadtime_comment !0
-; CHECK-NEXT: @llvm.compiler.used = appending global [1 x ptr] [ptr @[[LOADTIME_COMMENT_STR]]], section "llvm.metadata"
+; CHECK: @.loadtime_comment_vars.str = private unnamed_addr constant [22 x i8] c"loadtime_comment vars\00", align 1
+; CHECK: @loadtime_comment_vars_gv = internal global ptr @.loadtime_comment_vars.str, align {{[0-9]+}}, !loadtime_comment !0
+; CHECK-NEXT: @llvm.compiler.used = appending global [2 x ptr] [ptr @[[LOADTIME_COMMENT_STR]], ptr @loadtime_comment_vars_gv], section "llvm.metadata"
-; Function has an implicit ref MD pointing at the string:
-; CHECK-O0: define void @f0() !implicit.ref ![[MD:[0-9]+]]
-; CHECK-ON: define void @f0() local_unnamed_addr #0 !implicit.ref ![[MD:[0-9]+]]
-
-; CHECK-O0: define i32 @main() !implicit.ref ![[MD]]
-; CHECK-ON: define noundef i32 @main() local_unnamed_addr #0 !implicit.ref ![[MD]]
+; Function has implicit refs to both loadtime comment globals.
+; CHECK-O0: define void @f0() !implicit.ref ![[MD:[0-9]+]] !implicit.ref ![[MD2:[0-9]+]]
+; CHECK-ON: define void @f0() local_unnamed_addr #0 !implicit.ref ![[MD:[0-9]+]] !implicit.ref ![[MD2:[0-9]+]]
+; CHECK-O0: define i32 @main() !implicit.ref ![[MD]] !implicit.ref ![[MD2]]
+; CHECK-ON: define noundef i32 @main() local_unnamed_addr #0 !implicit.ref ![[MD]] !implicit.ref ![[MD2]]
; Verify metadata content
; CHECK-O0: ![[MD]] = !{ptr @[[LOADTIME_COMMENT_STR]]}
; CHECK-ON: ![[MD]] = !{ptr @[[LOADTIME_COMMENT_STR]]}
+; CHECK-O0: ![[MD2]] = !{ptr @loadtime_comment_vars_gv}
+; CHECK-ON: ![[MD2]] = !{ptr @loadtime_comment_vars_gv}
>From 920e3d7f8154f5753caeb148ea1da080ddb9ebd8 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 22 Jun 2026 14:30:46 +0530
Subject: [PATCH 2/7] [Clang][AIX] Handle -mloadtime-comment-vars in global var
emission
---
clang/docs/LanguageExtensions.md | 17 +-
clang/lib/CodeGen/CodeGenModule.cpp | 150 +++++++-----------
clang/lib/CodeGen/CodeGenModule.h | 15 +-
.../CodeGen/loadtime-comment-vars-cxx.cpp | 85 ++++++++++
clang/test/CodeGen/loadtime-comment-vars.c | 10 +-
clang/test/Driver/mloadtime-comment-vars.c | 4 +
6 files changed, 176 insertions(+), 105 deletions(-)
create mode 100644 clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 410647270f2f5..9db01587a98b4 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6514,8 +6514,9 @@ that interface unit. Importing TUs do not re-emit the string.
The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
global variable names that should be preserved in the final object file as
-loadtime identifying strings. This is an AIX-specific feature and is ignored
-on other targets.
+loadtime identifying strings. This is an AIX-specific feature; on other
+targets the compiler emits a warning and the flag is not forwarded to
+`-cc1`.
This flag complements `#pragma comment(copyright, ...)` for codebases that
already use the traditional UNIX convention of embedding identifying strings
@@ -6527,6 +6528,18 @@ Syntax:
-mloadtime-comment-vars=<var1>[,<var2>,...]
```
+Name matching:
+
+- In C, names are matched as plain identifiers (for example, `sccsid`).
+- In C++, names containing `::` are treated as source-qualified names and
+ matched against the declaration's qualified source name (for example,
+ `N::x` or `A::x`).
+- In C++, names without `::` are treated as unqualified names and matched by
+ plain identifier. This may match more than one declaration when names are
+ reused across scopes.
+- To target a single declaration in C++, prefer qualified names. Unqualified
+ matches can preserve additional globals and increase object size.
+
Valid variable types:
A variable named in the list must meet both of these conditions to be
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index a20d54b3cf16d..89d653653450a 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1133,12 +1133,6 @@ void CodeGenModule::Release() {
if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
EmitModuleInitializers(Primary);
- // Queue loadtime comment variable candidates into the deferred emission
- // list before EmitDeferred() runs, so their initializers (which may
- // reference other globals, e.g. static const char *p = a;) are emitted
- // through the normal infrastructure with correct ordering.
- QueueLoadTimeCommentVarEmission();
-
EmitDeferred();
DeferredDecls.insert_range(EmittedDeferredDecls);
EmittedDeferredDecls.clear();
@@ -1833,9 +1827,6 @@ void CodeGenModule::Release() {
EmitBackendOptionsMetadata(getCodeGenOpts());
- // Mark loadtime comment variables specified via -mloadtime-comment-vars.
- ProcessLoadTimeCommentVars();
-
// If there is device offloading code embed it in the host now.
EmbedObject(&getModule(), CodeGenOpts, *getFileSystem(), getDiags());
@@ -4337,7 +4328,12 @@ bool CodeGenModule::MustBeEmitted(const ValueDecl *Global) {
(VD->getStorageDuration() == SD_Static ||
VD->getStorageDuration() == SD_Thread)) ||
(CodeGenOpts.KeepStaticConsts && VD->getStorageDuration() == SD_Static &&
- VD->getType().isConstQualified())))
+ VD->getType().isConstQualified()) ||
+ // Keep requested loadtime-comment variables in the normal
+ // emission path so EmitGlobalVarDefinition can annotate the definition.
+ (getTriple().isOSAIX() && !CodeGenOpts.LoadTimeCommentVars.empty() &&
+ isLoadTimeCommentCandidateVariable(VD,
+ CodeGenOpts.LoadTimeCommentVars))))
return true;
return getContext().DeclMustBeEmitted(Global);
@@ -4399,23 +4395,19 @@ bool CodeGenModule::MayBeEmittedEagerly(const ValueDecl *Global) {
return true;
}
-/// Check if a variable declaration is suitable to be treated as a loadtime
-/// comment variable. Valid variables must be character pointers or character
-/// arrays with an initializer.
+/// Return true if a variable is a supported loadtime-comment declaration:
+/// character pointer/array with an initializer.
bool CodeGenModule::isValidLoadTimeCommentVariable(const VarDecl *D) const {
- // Must be a valid declaration and must have an initializer (the string).
if (!D || !D->hasInit())
return false;
QualType Ty = D->getType();
- // 1. Handle Pointers (e.g., char *sccsid, const char *copyright).
if (const PointerType *PT = Ty->getAs<PointerType>()) {
if (PT->getPointeeType()->isAnyCharacterType())
return true;
}
- // 2. Handle Arrays (e.g., char version[])
if (const ArrayType *AT = getContext().getAsArrayType(Ty)) {
if (AT->getElementType()->isAnyCharacterType())
return true;
@@ -4424,88 +4416,57 @@ bool CodeGenModule::isValidLoadTimeCommentVariable(const VarDecl *D) const {
return false; // Reject ints, structs, etc.
}
-/// Check if a variable is eligible to be treated as a loadtime comment
-/// variable. This requires: (1) the variable name is in the requested list
-/// and (2) the variable type is valid (char pointer or array with initializer).
-bool CodeGenModule::isLoadTimeCommentCandidateVariable(
- const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars) {
- if (!llvm::is_contained(LoadTimeCommentVars, VD->getName()))
+/// Return true if a variable name matches any entry in LoadTimeCommentVars.
+///
+/// - A token containing "::" is treated as a source-qualified name.
+/// - A token without "::" is treated as an unqualified identifier and may
+/// match declarations in multiple scopes.
+///
+/// For qualified matching, leading "::" is ignored on both sides, so "::x"
+/// and "x" both select a file-scope variable.
+bool CodeGenModule::matchesLoadTimeCommentVarName(
+ const VarDecl *VD,
+ const std::vector<std::string> &LoadTimeCommentVars) const {
+ if (!VD)
return false;
- return isValidLoadTimeCommentVariable(VD);
-}
-/// QueueLoadTimeCommentVarEmission: Called before EmitDeferred().
-/// Move loadtime comment variable candidates from DeferredDecls into
-/// DeferredDeclsToEmit so that the normal deferred emission machinery
-/// defines them — including any globals their initializers reference
-/// (e.g. static const char *p = a;).
-void CodeGenModule::QueueLoadTimeCommentVarEmission() {
- if (!getTriple().isOSAIX())
- return;
-
- const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
- if (LoadTimeCommentVars.empty())
- return;
+ StringRef Unqualified = VD->getName();
+ std::optional<std::string> Qualified;
- TranslationUnitDecl *TU = getContext().getTranslationUnitDecl();
- for (auto *D : TU->decls()) {
- auto *VD = dyn_cast<VarDecl>(D);
- if (!VD)
+ for (const std::string &RequestedName : LoadTimeCommentVars) {
+ StringRef Requested(RequestedName);
+ if (Requested.empty())
continue;
- if (!isLoadTimeCommentCandidateVariable(VD, LoadTimeCommentVars))
- continue;
-
- // Move the decl from DeferredDecls -> DeferredDeclsToEmit so EmitDeferred
- // will define it. If it is already being emitted (e.g. it is referenced
- // somewhere), this is a harmless duplicate that EmitDeferred ignores.
- GlobalDecl GD(VD);
- StringRef MangledName = getMangledName(GD);
- auto DDI = DeferredDecls.find(MangledName);
- if (DDI != DeferredDecls.end()) {
- addDeferredDeclToEmit(DDI->second);
- DeferredDecls.erase(DDI);
- }
- }
-}
-
-/// ProcessLoadTimeCommentVars: Called after EmitDeferred().
-/// Attach loadtime_comment metadata and add each variable to
-/// llvm.compiler.used. By this point the deferred emission loop has already
-/// defined the globals, so we only need to look them up and annotate them. Only
-/// valid on AIX targets.
-void CodeGenModule::ProcessLoadTimeCommentVars() {
- if (!getTriple().isOSAIX())
- return;
- const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
- if (LoadTimeCommentVars.empty())
- return;
-
- auto &C = getLLVMContext();
- TranslationUnitDecl *TU = getContext().getTranslationUnitDecl();
-
- for (auto *D : TU->decls()) {
- auto *VD = dyn_cast<VarDecl>(D);
- if (!VD)
- continue;
- if (!isLoadTimeCommentCandidateVariable(VD, LoadTimeCommentVars))
- continue;
-
- // Look up the LLVM global that EmitDeferred() should have defined.
- llvm::GlobalValue *GV = GetGlobalValue(getMangledName(GlobalDecl(VD)));
- if (!GV || GV->isDeclaration())
+ if (Requested.contains("::")) {
+ if (!Qualified) {
+ Qualified = VD->getQualifiedNameAsString();
+ // Normalize file-scope names by dropping a leading "::".
+ if (StringRef(*Qualified).starts_with("::"))
+ Qualified->erase(0, 2);
+ }
+ Requested.consume_front("::");
+ if (Requested == *Qualified)
+ return true;
continue;
+ }
- auto *GVar = dyn_cast<llvm::GlobalVariable>(GV);
- if (!GVar)
- continue;
+ if (Requested == Unqualified)
+ return true;
+ }
- // Mark with loadtime_comment metadata for LowerCommentStringPass.
- GVar->setMetadata("loadtime_comment", llvm::MDNode::get(C, {}));
+ return false;
+}
- // Prevent the optimizer from removing the global variable.
- llvm::appendToCompilerUsed(getModule(), {GVar});
- }
+/// Check if a variable is eligible to be treated as a loadtime comment
+/// variable. This requires: (1) the variable name is in the requested list
+/// and (2) the variable type is valid (char pointer or array with initializer).
+bool CodeGenModule::isLoadTimeCommentCandidateVariable(
+ const VarDecl *VD,
+ const std::vector<std::string> &LoadTimeCommentVars) const {
+ if (!isValidLoadTimeCommentVariable(VD))
+ return false;
+ return matchesLoadTimeCommentVarName(VD, LoadTimeCommentVars);
}
ConstantAddress CodeGenModule::GetAddrOfMSGuidDecl(const MSGuidDecl *GD) {
@@ -6647,6 +6608,17 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
if (D->hasAttr<AnnotateAttr>())
AddGlobalAnnotations(D, GV);
+ if (getTriple().isOSAIX()) {
+ const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
+ if (!LoadTimeCommentVars.empty() &&
+ isLoadTimeCommentCandidateVariable(D, LoadTimeCommentVars)) {
+ auto &C = getLLVMContext();
+ // Mark for LowerCommentStringPass and keep the symbol alive.
+ GV->setMetadata("loadtime_comment", llvm::MDNode::get(C, {}));
+ llvm::appendToCompilerUsed(getModule(), {GV});
+ }
+ }
+
// Set the llvm linkage type as appropriate.
llvm::GlobalValue::LinkageTypes Linkage = getLLVMLinkageVarDefinition(D);
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index f6ea77860a9a2..d703ca4dd820a 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2205,16 +2205,13 @@ class CodeGenModule : public CodeGenTypeCache {
/// Check if a variable is eligible to be treated as a loadtime comment
/// variable (must be in the requested list and have a valid char type).
bool isLoadTimeCommentCandidateVariable(
- const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
+ const VarDecl *VD,
+ const std::vector<std::string> &LoadTimeCommentVars) const;
- /// Queue loadtime comment variable candidates into the deferred
- /// emission list before EmitDeferred() so their initializers are emitted
- /// through the normal infrastructure with correct ordering.
- void QueueLoadTimeCommentVarEmission();
-
- /// Attach loadtime_comment metadata and add variables to
- /// llvm.compiler.used after EmitDeferred() has defined them.
- void ProcessLoadTimeCommentVars();
+ /// Check if a variable name matches any entry in LoadTimeCommentVars.
+ bool matchesLoadTimeCommentVarName(
+ const VarDecl *VD,
+ const std::vector<std::string> &LoadTimeCommentVars) const;
};
} // end namespace CodeGen
diff --git a/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
new file mode 100644
index 0000000000000..57a0f84d4e170
--- /dev/null
+++ b/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
@@ -0,0 +1,85 @@
+// RUN: %clang_cc1 -std=c++17 -O2 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=x,N::q,A::x,N::ptr,B::ver,C::info \
+// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+
+// 1. Unqualified name "x" — matches both ::x (file scope) and N::x (namespace)
+char x[] = "@(#) global x";
+
+namespace N {
+char x[] = "@(#) ns x";
+
+// 2. Qualified name "N::q" — selects only this declaration
+char q[] = "@(#) ns q";
+
+
+// 3. Deferred pointer-chain inside a namespace.
+// N::ptr points to N::base (another static). MustBeEmitted forces N::ptr
+// through EmitGlobalVarDefinition; the initializer reference to N::base
+// causes N::base to be emitted as a side-effect.
+static const char base[] = "base deferred ns";
+static const char *ptr = base;
+} // namespace N
+
+
+// 4. Qualified name "A::x" — class static member (const char *)
+struct A {
+ static const char *x;
+};
+const char *A::x = "@(#) class x";
+
+
+// 5. Deferred pointer-chain for a class static member.
+// B::ver points to a separate static array base_b.
+struct B {
+ static const char *ver;
+};
+static const char base_b[] = "base for B::ver";
+const char *B::ver = base_b;
+
+// 6. Qualified name in list but only declared, never defined — must be skipped.
+struct C { static const char *info; };
+// C::info has no definition in this TU.
+
+
+// 7. Invalid type — int with a matching name should NOT be tagged.
+int not_string = 7;
+
+void f() {}
+
+// --- Checks ----------------------------------------------------------------
+
+// Unqualified "x" matches both ::x and N::x.
+// CHECK-DAG: @x = global [14 x i8] c"@(#) global x\00", align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK-DAG: @_ZN1N1xE = global [10 x i8] c"@(#) ns x\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+
+// Qualified "N::q" selects the specific namespace member.
+// CHECK-DAG: @_ZN1N1qE = global [10 x i8] c"@(#) ns q\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+
+// Qualified "A::x" selects the class static member (pointer to literal).
+// CHECK-DAG: @[[AX:_ZN1A1xE]] = {{.*}}global ptr @[[AXSTR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[AXSTR]] = private unnamed_addr constant [13 x i8] c"@(#) class x\00", align {{[0-9]+}}
+
+// Deferred: N::ptr points to N::base — both must be emitted.
+// CHECK-DAG: @_ZN1NL3ptrE = internal global ptr @_ZN1NL4baseE, align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @_ZN1NL4baseE = internal constant [17 x i8] c"base deferred ns\00", align {{[0-9]+}}
+
+// Deferred: B::ver points to base_b — both must be emitted.
+// CHECK-DAG: @_ZN1B3verE = global ptr @_ZL6base_b, align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @_ZL6base_b = internal constant [16 x i8] c"base for B::ver\00", align {{[0-9]+}}
+
+// Invalid type must not be tagged.
+// CHECK-NOT: @not_string{{.*}}!loadtime_comment
+
+// C::info is declared but not defined — must not appear at all.
+// CHECK-NOT: @_ZN1C4infoE
+
+// All six selected globals are preserved in llvm.compiler.used.
+// CHECK: @llvm.compiler.used = appending global [6 x ptr]
+// CHECK-SAME: @x
+// CHECK-SAME: @_ZN1N1xE
+// CHECK-SAME: @_ZN1N1qE
+// CHECK-SAME: @_ZN1NL3ptrE
+// CHECK-SAME: @[[AX]]
+// CHECK-SAME: @_ZN1B3verE
+// CHECK-SAME: section "llvm.metadata"
diff --git a/clang/test/CodeGen/loadtime-comment-vars.c b/clang/test/CodeGen/loadtime-comment-vars.c
index d54f848ca2eea..057c39f4f8380 100644
--- a/clang/test/CodeGen/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/loadtime-comment-vars.c
@@ -35,13 +35,13 @@ extern char *not_defined_here;
void foo() {}
-// CHECK-DAG: @active = internal global ptr @.str, align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
-// CHECK: @.str = private unnamed_addr constant [19 x i8] c"@(#) active string\00", align {{[0-9]+}}
-// CHECK-DAG: @sccsid = internal global ptr @.str.1, align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK: @.str.1 = private unnamed_addr constant [24 x i8] c"@(#) sccsid Version 1.0\00", align {{[0-9]+}}
+// CHECK-DAG: @[[ACTIVE:active]] = internal global ptr @[[ACTIVE_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK-DAG: @[[ACTIVE_STR]] = private unnamed_addr constant [19 x i8] c"@(#) active string\00", align {{[0-9]+}}
+// CHECK-DAG: @sccsid = internal global ptr @[[SCCSID_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[SCCSID_STR]] = private unnamed_addr constant [24 x i8] c"@(#) sccsid Version 1.0\00", align {{[0-9]+}}
// CHECK-DAG: @version = internal global [27 x i8] c"@(#) Copyright Version 2.0\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @same_copyright = internal global ptr @dummy, align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK: @dummy = internal constant [25 x i8] c"dummy copyright deferred\00"
+// CHECK-DAG: @dummy = internal constant [25 x i8] c"dummy copyright deferred\00"
// CHECK: @llvm.compiler.used = appending global [4 x ptr]
// CHECK-SAME: ptr @sccsid
// CHECK-SAME: ptr @version
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
index a443c85aec1f7..4c5cfc586dab2 100644
--- a/clang/test/Driver/mloadtime-comment-vars.c
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -1,9 +1,13 @@
// RUN: %clang -### -target powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s
+// RUN: %clang -### -target powerpc64-ibm-aix -mloadtime-comment-vars=::x,N::x,A::x %s 2>&1 | FileCheck %s --check-prefix=SCOPE
// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s --check-prefix=NONAIX
// CHECK: "-cc1"
// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
+// SCOPE: "-cc1"
+// SCOPE-SAME: "-mloadtime-comment-vars=::x,N::x,A::x"
+
// NONAIX: warning: ignoring '-mloadtime-comment-vars=sccsid,version' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
// NONAIX: "-cc1"
// NONAIX-NOT: "-mloadtime-comment-vars=sccsid,version"
>From 06384fcfeb14038f6518ca3551dec295d79a8b3a Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 25 Jun 2026 12:56:50 +0530
Subject: [PATCH 3/7] [Clang][AIX] Switch -mloadtime-comment-vars name matching
to mangled IR names
Replace source-qualified name matching in matchesLoadTimeCommentVarName with
mangled IR symbol name matching via getMangledName(GlobalDecl(VD)).
---
clang/docs/LanguageExtensions.md | 33 ++++++++----
clang/lib/CodeGen/CodeGenModule.cpp | 53 +++++--------------
clang/lib/CodeGen/CodeGenModule.h | 11 ++--
.../CodeGen/loadtime-comment-vars-cxx.cpp | 52 ++++++++++--------
clang/test/Driver/mloadtime-comment-vars.c | 6 +--
5 files changed, 74 insertions(+), 81 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 9db01587a98b4..eec28dd75cc74 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6530,15 +6530,30 @@ Syntax:
Name matching:
-- In C, names are matched as plain identifiers (for example, `sccsid`).
-- In C++, names containing `::` are treated as source-qualified names and
- matched against the declaration's qualified source name (for example,
- `N::x` or `A::x`).
-- In C++, names without `::` are treated as unqualified names and matched by
- plain identifier. This may match more than one declaration when names are
- reused across scopes.
-- To target a single declaration in C++, prefer qualified names. Unqualified
- matches can preserve additional globals and increase object size.
+Names are matched against the variable's **mangled IR symbol name** — the
+name as it appears in the object file.
+
+- In C, file-scope static variables are not mangled, so the mangled name is
+ identical to the source identifier (for example, `sccsid`).
+- In C++, variables are mangled using the Itanium ABI. To find the mangled
+ name, compile with `clang -S -emit-llvm` and look for the global in the
+ `.ll` output, or run `nm` on the object file.
+
+```console
+# Find the mangled name of a C++ variable
+$ clang++ -S -emit-llvm -o - source.cpp | grep '@.*sccsid'
+ at _ZN1N6sccsidE = ...
+
+# Or use nm on the object file
+$ nm source.o | grep sccsid
+0000000000000000 b _ZN1N6sccsidE
+
+# Then pass the mangled name to the flag
+-mloadtime-comment-vars=_ZN1N6sccsidE
+```
+
+Mangled names are unique, so each entry in the list selects exactly one
+variable. Unrecognised names are silently ignored.
Valid variable types:
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 89d653653450a..855fcbfccd817 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4416,54 +4416,27 @@ bool CodeGenModule::isValidLoadTimeCommentVariable(const VarDecl *D) const {
return false; // Reject ints, structs, etc.
}
-/// Return true if a variable name matches any entry in LoadTimeCommentVars.
+/// Return true if the mangled IR name of Global Variable matches any entry in
+/// LoadTimeCommentVars list. Users supply the mangled name as it appears in the
+/// object file.
///
-/// - A token containing "::" is treated as a source-qualified name.
-/// - A token without "::" is treated as an unqualified identifier and may
-/// match declarations in multiple scopes.
-///
-/// For qualified matching, leading "::" is ignored on both sides, so "::x"
-/// and "x" both select a file-scope variable.
+/// For plain C file-scope statics the mangled name is identical to the
+/// source identifier (e.g. ``sccsid``). For C++ variables the mangled name
+/// is the Itanium ABI symbol (e.g. ``_ZN1N6sccsidE``).
bool CodeGenModule::matchesLoadTimeCommentVarName(
- const VarDecl *VD,
- const std::vector<std::string> &LoadTimeCommentVars) const {
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars) {
if (!VD)
return false;
-
- StringRef Unqualified = VD->getName();
- std::optional<std::string> Qualified;
-
- for (const std::string &RequestedName : LoadTimeCommentVars) {
- StringRef Requested(RequestedName);
- if (Requested.empty())
- continue;
-
- if (Requested.contains("::")) {
- if (!Qualified) {
- Qualified = VD->getQualifiedNameAsString();
- // Normalize file-scope names by dropping a leading "::".
- if (StringRef(*Qualified).starts_with("::"))
- Qualified->erase(0, 2);
- }
- Requested.consume_front("::");
- if (Requested == *Qualified)
- return true;
- continue;
- }
-
- if (Requested == Unqualified)
- return true;
- }
-
- return false;
+ StringRef MangledName = getMangledName(GlobalDecl(VD));
+ return llvm::is_contained(LoadTimeCommentVars, MangledName);
}
/// Check if a variable is eligible to be treated as a loadtime comment
-/// variable. This requires: (1) the variable name is in the requested list
-/// and (2) the variable type is valid (char pointer or array with initializer).
+/// variable. This requires: (1) the variable's mangled name is in the
+/// requested list and (2) the variable type is valid (char pointer or array
+/// with initializer).
bool CodeGenModule::isLoadTimeCommentCandidateVariable(
- const VarDecl *VD,
- const std::vector<std::string> &LoadTimeCommentVars) const {
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars) {
if (!isValidLoadTimeCommentVariable(VD))
return false;
return matchesLoadTimeCommentVarName(VD, LoadTimeCommentVars);
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index d703ca4dd820a..75132234f29eb 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2203,15 +2203,14 @@ class CodeGenModule : public CodeGenTypeCache {
bool isValidLoadTimeCommentVariable(const VarDecl *D) const;
/// Check if a variable is eligible to be treated as a loadtime comment
- /// variable (must be in the requested list and have a valid char type).
+ /// variable (must be in the requested list and have a valid type).
bool isLoadTimeCommentCandidateVariable(
- const VarDecl *VD,
- const std::vector<std::string> &LoadTimeCommentVars) const;
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
- /// Check if a variable name matches any entry in LoadTimeCommentVars.
+ /// Return true if the mangled IR name of a Global Variable matches any entry
+ /// in LoadTimeCommentVars list.
bool matchesLoadTimeCommentVarName(
- const VarDecl *VD,
- const std::vector<std::string> &LoadTimeCommentVars) const;
+ const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
};
} // end namespace CodeGen
diff --git a/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
index 57a0f84d4e170..01a19b96f8ba1 100644
--- a/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
+++ b/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
@@ -1,77 +1,85 @@
+// Names are matched against mangled IR symbol names.
+// C++ variables use Itanium ABI mangling; C/file-scope statics keep their
+// source name.
+//
+// Mangled names used here:
+// x -> x (file-scope, no mangling)
+// N::x -> _ZN1N1xE
+// N::q -> _ZN1N1qE
+// N::ptr -> _ZN1NL3ptrE (static, internal linkage)
+// A::x -> _ZN1A1xE
+// B::ver -> _ZN1B3verE
+// C::info -> _ZN1C4infoE (declared only, no definition — skipped)
+
// RUN: %clang_cc1 -std=c++17 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=x,N::q,A::x,N::ptr,B::ver,C::info \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1N1qE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
-
-// 1. Unqualified name "x" — matches both ::x (file scope) and N::x (namespace)
+// 1. File-scope array "x" — no mangling in C++, IR name == source name.
char x[] = "@(#) global x";
namespace N {
char x[] = "@(#) ns x";
-// 2. Qualified name "N::q" — selects only this declaration
+// 2. Namespace member "N::x" — mangled as _ZN1N1xE.
char q[] = "@(#) ns q";
-
// 3. Deferred pointer-chain inside a namespace.
-// N::ptr points to N::base (another static). MustBeEmitted forces N::ptr
-// through EmitGlobalVarDefinition; the initializer reference to N::base
-// causes N::base to be emitted as a side-effect.
+// _ZN1NL3ptrE (N::ptr) points to _ZN1NL4baseE (N::base, another static).
+// MustBeEmitted forces N::ptr through EmitGlobalVarDefinition; the
+// initializer reference to N::base causes N::base to be emitted too.
static const char base[] = "base deferred ns";
static const char *ptr = base;
} // namespace N
-
-// 4. Qualified name "A::x" — class static member (const char *)
+// 4. Class static member "A::x" — mangled as _ZN1A1xE.
struct A {
static const char *x;
};
const char *A::x = "@(#) class x";
-
// 5. Deferred pointer-chain for a class static member.
-// B::ver points to a separate static array base_b.
+// _ZN1B3verE (B::ver) points to _ZL6base_b.
struct B {
static const char *ver;
};
static const char base_b[] = "base for B::ver";
const char *B::ver = base_b;
-// 6. Qualified name in list but only declared, never defined — must be skipped.
+// 6. _ZN1C4infoE is in the list but C::info has no definition in this TU —
+// must be silently skipped.
struct C { static const char *info; };
-// C::info has no definition in this TU.
-
-// 7. Invalid type — int with a matching name should NOT be tagged.
+// 7. Invalid type — int must not be tagged regardless of its IR name.
int not_string = 7;
void f() {}
// --- Checks ----------------------------------------------------------------
-// Unqualified "x" matches both ::x and N::x.
+// File-scope x and namespace N::x both matched.
// CHECK-DAG: @x = global [14 x i8] c"@(#) global x\00", align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
// CHECK-DAG: @_ZN1N1xE = global [10 x i8] c"@(#) ns x\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// Qualified "N::q" selects the specific namespace member.
+// N::q matched by mangled name _ZN1N1qE.
// CHECK-DAG: @_ZN1N1qE = global [10 x i8] c"@(#) ns q\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// Qualified "A::x" selects the class static member (pointer to literal).
+// A::x matched by mangled name _ZN1A1xE.
// CHECK-DAG: @[[AX:_ZN1A1xE]] = {{.*}}global ptr @[[AXSTR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[AXSTR]] = private unnamed_addr constant [13 x i8] c"@(#) class x\00", align {{[0-9]+}}
-// Deferred: N::ptr points to N::base — both must be emitted.
+// Deferred: N::ptr (_ZN1NL3ptrE) points to N::base (_ZN1NL4baseE).
// CHECK-DAG: @_ZN1NL3ptrE = internal global ptr @_ZN1NL4baseE, align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @_ZN1NL4baseE = internal constant [17 x i8] c"base deferred ns\00", align {{[0-9]+}}
-// Deferred: B::ver points to base_b — both must be emitted.
+// Deferred: B::ver (_ZN1B3verE) points to base_b (_ZL6base_b).
// CHECK-DAG: @_ZN1B3verE = global ptr @_ZL6base_b, align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @_ZL6base_b = internal constant [16 x i8] c"base for B::ver\00", align {{[0-9]+}}
// Invalid type must not be tagged.
// CHECK-NOT: @not_string{{.*}}!loadtime_comment
-// C::info is declared but not defined — must not appear at all.
+// C::info has no definition — must not appear.
// CHECK-NOT: @_ZN1C4infoE
// All six selected globals are preserved in llvm.compiler.used.
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
index 4c5cfc586dab2..77d77e2552376 100644
--- a/clang/test/Driver/mloadtime-comment-vars.c
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -1,13 +1,11 @@
// RUN: %clang -### -target powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s
-// RUN: %clang -### -target powerpc64-ibm-aix -mloadtime-comment-vars=::x,N::x,A::x %s 2>&1 | FileCheck %s --check-prefix=SCOPE
// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s --check-prefix=NONAIX
+// Verify the option is forwarded verbatim to cc1 on AIX.
// CHECK: "-cc1"
// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
-// SCOPE: "-cc1"
-// SCOPE-SAME: "-mloadtime-comment-vars=::x,N::x,A::x"
-
+// Verify a warning is emitted and the option is NOT forwarded on non-AIX targets.
// NONAIX: warning: ignoring '-mloadtime-comment-vars=sccsid,version' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
// NONAIX: "-cc1"
// NONAIX-NOT: "-mloadtime-comment-vars=sccsid,version"
>From f629cdd4f7acd09728760d105679d2a4bc696343 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tonypalampalliyil at gmail.com>
Date: Thu, 25 Jun 2026 21:03:23 +0530
Subject: [PATCH 4/7] Apply suggestions from code review
Co-authored-by: Hubert Tong <hubert.reinterpretcast at gmail.com>
---
clang/docs/LanguageExtensions.md | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index eec28dd75cc74..bee91bdf24f7c 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6515,8 +6515,7 @@ that interface unit. Importing TUs do not re-emit the string.
The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
global variable names that should be preserved in the final object file as
loadtime identifying strings. This is an AIX-specific feature; on other
-targets the compiler emits a warning and the flag is not forwarded to
-`-cc1`.
+targets the compiler emits a warning.
This flag complements `#pragma comment(copyright, ...)` for codebases that
already use the traditional UNIX convention of embedding identifying strings
@@ -6530,14 +6529,10 @@ Syntax:
Name matching:
-Names are matched against the variable's **mangled IR symbol name** — the
-name as it appears in the object file.
+Names are matched against the variable's mangled name.
- In C, file-scope static variables are not mangled, so the mangled name is
identical to the source identifier (for example, `sccsid`).
-- In C++, variables are mangled using the Itanium ABI. To find the mangled
- name, compile with `clang -S -emit-llvm` and look for the global in the
- `.ll` output, or run `nm` on the object file.
```console
# Find the mangled name of a C++ variable
@@ -6553,7 +6548,6 @@ $ nm source.o | grep sccsid
```
Mangled names are unique, so each entry in the list selects exactly one
-variable. Unrecognised names are silently ignored.
Valid variable types:
>From e83fa0eee4cbb31a447d715d41695970d092e4ba Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 26 Jun 2026 15:54:01 +0530
Subject: [PATCH 5/7] [Clang][AIX] Diagnose unsupported -mloadtime-comment-vars
variables
---
clang/docs/LanguageExtensions.md | 61 ++---
clang/include/clang/Basic/CodeGenOptions.h | 2 +-
.../clang/Basic/DiagnosticFrontendKinds.td | 17 ++
clang/include/clang/Basic/DiagnosticGroups.td | 4 +
clang/include/clang/Basic/DiagnosticIDs.h | 2 +-
clang/include/clang/Options/Options.td | 4 +-
clang/lib/CodeGen/CodeGenModule.cpp | 129 +++++++---
clang/lib/CodeGen/CodeGenModule.h | 42 +++-
.../PowerPC/loadtime-comment-vars-cxx.cpp | 228 ++++++++++++++++++
.../{ => PowerPC}/loadtime-comment-vars.c | 19 +-
.../CodeGen/loadtime-comment-vars-cxx.cpp | 93 -------
11 files changed, 421 insertions(+), 180 deletions(-)
create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
rename clang/test/CodeGen/{ => PowerPC}/loadtime-comment-vars.c (80%)
delete mode 100644 clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index bee91bdf24f7c..51e6d7c9e3430 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6513,9 +6513,10 @@ that interface unit. Importing TUs do not re-emit the string.
### Preserving Identifying Variables with -mloadtime-comment-vars
The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
-global variable names that should be preserved in the final object file as
+mangled variable names that should be preserved in the final object file as
loadtime identifying strings. This is an AIX-specific feature; on other
-targets the compiler emits a warning.
+targets the compiler emits a warning. Names are matched against each
+variable's mangled name, and unrecognised names are silently ignored.
This flag complements `#pragma comment(copyright, ...)` for codebases that
already use the traditional UNIX convention of embedding identifying strings
@@ -6527,40 +6528,45 @@ Syntax:
-mloadtime-comment-vars=<var1>[,<var2>,...]
```
-Name matching:
+In C, variable names are not mangled, so the mangled name is identical to the source
+identifier (for example, `sccsid`). In C++, the mangled name follows the
+Itanium C++ ABI, so a namespace-scoped or class-scoped variable must be named
+using its mangled form:
-Names are matched against the variable's mangled name.
-
-- In C, file-scope static variables are not mangled, so the mangled name is
- identical to the source identifier (for example, `sccsid`).
+```c++
+namespace N { char sccsid[] = "@(#) MyApp Version 1.0"; } // N::sccsid -> _ZN1N6sccsidE
+const char *App::version = "@(#) Built 2026-06-25"; // App::version -> _ZN3App7versionE
+```
```console
-# Find the mangled name of a C++ variable
-$ clang++ -S -emit-llvm -o - source.cpp | grep '@.*sccsid'
- at _ZN1N6sccsidE = ...
-
-# Or use nm on the object file
-$ nm source.o | grep sccsid
-0000000000000000 b _ZN1N6sccsidE
-
-# Then pass the mangled name to the flag
--mloadtime-comment-vars=_ZN1N6sccsidE
+-mloadtime-comment-vars=_ZN1N6sccsidE,_ZN3App7versionE
```
-Mangled names are unique, so each entry in the list selects exactly one
-
Valid variable types:
-A variable named in the list must meet both of these conditions to be
+A variable named in the list must meet all of these conditions to be
preserved:
+- It must be defined at file, namespace, or class scope (a function-local
+ `static` variable is not supported).
- Its type must be a character pointer (`char *`, `const char *`) or a
- character array (`char[]`).
-- It must have an initializer.
-
-Variables that fail either check -- for example, an `int` or a `struct` --
-are silently skipped. Variables that appear in the list but are not defined in
-the translation unit are also ignored.
+ character array (`char[]`, `const char[]`).
+- It must have static storage duration and must not be `volatile`-qualified.
+- It must be constant-initialized, so that the string is present in the object
+ at load time. A dynamically initialized variable (whose value is computed by
+ a start-up constructor) is not preserved.
+- A character *pointer* must be initialized directly with a string literal (for
+ example, `char *p = "@(#) ...";`). A pointer bound to some other object
+ -- even a constant one, such as another character array -- does not itself
+ carry the identifying string and is not preserved.
+
+A variable that is named in the list but is `volatile`-qualified, does not
+have static storage duration (for example, a `thread_local` variable), is
+dynamically initialized, or is a pointer not bound to a string literal, is
+diagnosed with a warning and is not preserved. Variables of an unsupported type
+-- for example, an `int` or a `struct` -- or without an initializer are
+silently skipped, as are function-local `static` variables and names that are
+not defined in the translation unit.
Example:
@@ -6579,8 +6585,7 @@ clang -target powerpc64-ibm-aix \
-c source.c -o source.o
```
-Both `sccsid` and `version` survive optimization and are retained in the
-object file.
+Both `sccsid` and `version` are retained in the object file.
```console
$ what source.o
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 768882d346c9b..2f29a9807c4b2 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -355,7 +355,7 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// A list of linker options to embed in the object file.
std::vector<std::string> LinkerOptions;
- /// List of global variable names to preserve as loadtime comment variables.
+ /// List of mangled variable names to preserve as loadtime comment variables.
std::vector<std::string> LoadTimeCommentVars;
/// Name of the profile file to use as output for -fprofile-instr-generate,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index cef2fc32a1642..a9591f65ce4f1 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -26,6 +26,23 @@ def err_fe_linking_module : Error<"cannot link module '%0': %1">, DefaultFatal;
def warn_fe_linking_module : Warning<"linking module '%0': %1">, InGroup<LinkerWarnings>;
def note_fe_linking_module : Note<"linking module '%0': %1">;
+def warn_loadtime_comment_var_volatile : Warning<
+ "%0 named in '-mloadtime-comment-vars=' is volatile-qualified and will not "
+ "be preserved">,
+ InGroup<LoadtimeCommentVar>;
+def warn_loadtime_comment_var_storage : Warning<
+ "%0 named in '-mloadtime-comment-vars=' does not have static storage "
+ "duration and will not be preserved">,
+ InGroup<LoadtimeCommentVar>;
+def warn_loadtime_comment_var_dynamic_init : Warning<
+ "%0 named in '-mloadtime-comment-vars=' is not constant-initialized and "
+ "will not be preserved">,
+ InGroup<LoadtimeCommentVar>;
+def warn_loadtime_comment_var_not_string_literal : Warning<
+ "pointer %0 named in '-mloadtime-comment-vars=' is not initialized with a "
+ "string literal and will not be preserved">,
+ InGroup<LoadtimeCommentVar>;
+
def warn_fe_frame_larger_than : Warning<"stack frame size (%0) exceeds limit (%1) in '%2'">,
BackendInfo, InGroup<BackendFrameLargerThan>;
def warn_fe_backend_frame_larger_than: Warning<"%0">,
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 79583534b9bbd..0fc5d82ef65d9 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1638,6 +1638,10 @@ def GccCompat : DiagGroup<"gcc-compat">;
// A warning group for warnings about code that may be incompatible on AIX.
def AIXCompat : DiagGroup<"aix-compat">;
+// A warning group for variables named in -mloadtime-comment-vars= that cannot
+// be preserved as loadtime identifying strings.
+def LoadtimeCommentVar : DiagGroup<"loadtime-comment-var">;
+
// Warnings for Microsoft extensions.
def MicrosoftCharize : DiagGroup<"microsoft-charize">;
def MicrosoftDrectveSection : DiagGroup<"microsoft-drectve-section">;
diff --git a/clang/include/clang/Basic/DiagnosticIDs.h b/clang/include/clang/Basic/DiagnosticIDs.h
index f71e47b5cba70..8e714efac46ae 100644
--- a/clang/include/clang/Basic/DiagnosticIDs.h
+++ b/clang/include/clang/Basic/DiagnosticIDs.h
@@ -36,7 +36,7 @@ enum class Group;
enum {
DIAG_SIZE_COMMON = 300,
DIAG_SIZE_DRIVER = 400,
- DIAG_SIZE_FRONTEND = 200,
+ DIAG_SIZE_FRONTEND = 300,
DIAG_SIZE_SERIALIZATION = 120,
DIAG_SIZE_LEX = 500,
DIAG_SIZE_PARSE = 800,
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index a24fbfc50960a..bfe5a1e82bb0d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4845,8 +4845,8 @@ def mloadtime_comment_vars_EQ
: CommaJoined<["-"], "mloadtime-comment-vars=">,
Group<m_Group>,
Visibility<[ClangOption, CC1Option]>,
- HelpText<"Comma-separated list of global variable names to treat as "
- "loadtime variables">,
+ HelpText<"Comma-separated list of mangled variable names to preserve as "
+ "loadtime identifying strings">,
MarshallingInfoStringVector<CodeGenOpts<"LoadTimeCommentVars">>;
def mdefault_visibility_export_mapping_EQ : Joined<["-"], "mdefault-visibility-export-mapping=">,
Values<"none,explicit,all">,
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 855fcbfccd817..b223423d41d58 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4329,11 +4329,7 @@ bool CodeGenModule::MustBeEmitted(const ValueDecl *Global) {
VD->getStorageDuration() == SD_Thread)) ||
(CodeGenOpts.KeepStaticConsts && VD->getStorageDuration() == SD_Static &&
VD->getType().isConstQualified()) ||
- // Keep requested loadtime-comment variables in the normal
- // emission path so EmitGlobalVarDefinition can annotate the definition.
- (getTriple().isOSAIX() && !CodeGenOpts.LoadTimeCommentVars.empty() &&
- isLoadTimeCommentCandidateVariable(VD,
- CodeGenOpts.LoadTimeCommentVars))))
+ isForcedLoadTimeCommentVar(VD)))
return true;
return getContext().DeclMustBeEmitted(Global);
@@ -4395,25 +4391,51 @@ bool CodeGenModule::MayBeEmittedEagerly(const ValueDecl *Global) {
return true;
}
-/// Return true if a variable is a supported loadtime-comment declaration:
-/// character pointer/array with an initializer.
-bool CodeGenModule::isValidLoadTimeCommentVariable(const VarDecl *D) const {
- if (!D || !D->hasInit())
- return false;
+/// Classify a variable whose mangled name matched the -mloadtime-comment-vars=
+/// list, deciding whether it can be preserved, must be diagnosed, or should be
+/// silently ignored.
+CodeGenModule::LoadTimeCommentVarKind
+CodeGenModule::classifyLoadTimeCommentVariable(const VarDecl *D) const {
+ if (!D)
+ return LoadTimeCommentVarKind::Skip;
+ // Only character pointers/arrays with an initializer are supported; the
+ // underlying character type is taken from the pointee or element type.
QualType Ty = D->getType();
-
- if (const PointerType *PT = Ty->getAs<PointerType>()) {
- if (PT->getPointeeType()->isAnyCharacterType())
- return true;
- }
-
- if (const ArrayType *AT = getContext().getAsArrayType(Ty)) {
- if (AT->getElementType()->isAnyCharacterType())
- return true;
- }
-
- return false; // Reject ints, structs, etc.
+ const PointerType *PT = Ty->getAs<PointerType>();
+ const ArrayType *AT = PT ? nullptr : getContext().getAsArrayType(Ty);
+ QualType Pointee = PT ? PT->getPointeeType()
+ : AT ? AT->getElementType()
+ : QualType();
+
+ // Unsupported type (int, struct, ...) or missing initializer: silently
+ // ignored, matching the documented behavior.
+ if (Pointee.isNull() || !Pointee->isAnyCharacterType() || !D->hasInit())
+ return LoadTimeCommentVarKind::Skip;
+
+ // The string must have static storage duration; thread-local and automatic
+ // variables are diagnosed and not preserved.
+ if (D->getStorageDuration() != SD_Static)
+ return LoadTimeCommentVarKind::BadStorage;
+
+ // A volatile string has no stable value to embed, whether the variable
+ // itself or the character it refers to is volatile-qualified.
+ if (Ty.isVolatileQualified() || Pointee.isVolatileQualified())
+ return LoadTimeCommentVarKind::Volatile;
+
+ // The string has to be present in the object at load time. A dynamically
+ // initialized variable only gets its value from a startup constructor, so
+ // the object would not contain the intended string.
+ if (!D->hasConstantInitialization())
+ return LoadTimeCommentVarKind::DynamicInit;
+
+ // For the pointer form, the variable must point directly at a string
+ // literal. A pointer initialized with some other (even constant) address
+ // does not carry the identifying string itself.
+ if (PT && !isa<StringLiteral>(D->getInit()->IgnoreParenImpCasts()))
+ return LoadTimeCommentVarKind::NotStringLiteral;
+
+ return LoadTimeCommentVarKind::Preserve;
}
/// Return true if the mangled IR name of Global Variable matches any entry in
@@ -4431,15 +4453,52 @@ bool CodeGenModule::matchesLoadTimeCommentVarName(
return llvm::is_contained(LoadTimeCommentVars, MangledName);
}
-/// Check if a variable is eligible to be treated as a loadtime comment
-/// variable. This requires: (1) the variable's mangled name is in the
-/// requested list and (2) the variable type is valid (char pointer or array
-/// with initializer).
-bool CodeGenModule::isLoadTimeCommentCandidateVariable(
- const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars) {
- if (!isValidLoadTimeCommentVariable(VD))
- return false;
- return matchesLoadTimeCommentVarName(VD, LoadTimeCommentVars);
+/// Return true if a variable named in -mloadtime-comment-vars= should be forced
+/// through the normal emission path, so EmitGlobalVarDefinition can preserve or
+/// diagnose it. Unsupported forms (wrong type or no initializer) are left to
+/// the usual rules.
+bool CodeGenModule::isForcedLoadTimeCommentVar(const VarDecl *VD) {
+ return getTriple().isOSAIX() && !CodeGenOpts.LoadTimeCommentVars.empty() &&
+ matchesLoadTimeCommentVarName(VD, CodeGenOpts.LoadTimeCommentVars) &&
+ classifyLoadTimeCommentVariable(VD) != LoadTimeCommentVarKind::Skip;
+}
+
+/// Apply the -mloadtime-comment-vars= request to a global variable whose
+/// mangled name has already matched an entry in the list. Unsupported forms
+/// (wrong type or no initializer) are silently skipped; other variables the
+/// feature cannot honor are diagnosed; valid character pointer/array
+/// definitions are marked for LowerCommentStringPass and kept alive.
+void CodeGenModule::handleLoadTimeCommentVariable(const VarDecl *D,
+ llvm::GlobalVariable *GV) {
+ if (!GV || !D)
+ return;
+ switch (classifyLoadTimeCommentVariable(D)) {
+ case LoadTimeCommentVarKind::Skip:
+ break;
+ case LoadTimeCommentVarKind::BadStorage:
+ Diags.Report(D->getLocation(), diag::warn_loadtime_comment_var_storage)
+ << D;
+ break;
+ case LoadTimeCommentVarKind::Volatile:
+ Diags.Report(D->getLocation(), diag::warn_loadtime_comment_var_volatile)
+ << D;
+ break;
+ case LoadTimeCommentVarKind::DynamicInit:
+ Diags.Report(D->getLocation(), diag::warn_loadtime_comment_var_dynamic_init)
+ << D;
+ break;
+ case LoadTimeCommentVarKind::NotStringLiteral:
+ Diags.Report(D->getLocation(),
+ diag::warn_loadtime_comment_var_not_string_literal)
+ << D;
+ break;
+ case LoadTimeCommentVarKind::Preserve:
+ // Mark for LowerCommentStringPass and keep the symbol alive.
+ GV->setMetadata("loadtime_comment",
+ llvm::MDNode::get(getLLVMContext(), {}));
+ llvm::appendToCompilerUsed(getModule(), {GV});
+ break;
+ }
}
ConstantAddress CodeGenModule::GetAddrOfMSGuidDecl(const MSGuidDecl *GD) {
@@ -6584,12 +6643,8 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
if (getTriple().isOSAIX()) {
const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
if (!LoadTimeCommentVars.empty() &&
- isLoadTimeCommentCandidateVariable(D, LoadTimeCommentVars)) {
- auto &C = getLLVMContext();
- // Mark for LowerCommentStringPass and keep the symbol alive.
- GV->setMetadata("loadtime_comment", llvm::MDNode::get(C, {}));
- llvm::appendToCompilerUsed(getModule(), {GV});
- }
+ matchesLoadTimeCommentVarName(D, LoadTimeCommentVars))
+ handleLoadTimeCommentVariable(D, GV);
}
// Set the llvm linkage type as appropriate.
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 75132234f29eb..9c646f625c15d 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2198,19 +2198,45 @@ class CodeGenModule : public CodeGenTypeCache {
/// offsetof.
void emitPFPFieldsWithEvaluatedOffset();
- /// Check if a variable declaration is suitable to be treated as a loadtime
- /// comment variable (must be a character pointer or array with initializer).
- bool isValidLoadTimeCommentVariable(const VarDecl *D) const;
+ /// Classification for variables named by -mloadtime-comment-vars=.
+ ///
+ /// This enum describes how code generation should handle a matched
+ /// variable after inspecting its type, storage duration, qualifiers, and
+ /// initializer.
+ enum class LoadTimeCommentVarKind {
+ Skip, ///< Unsupported type or missing initializer: ignore silently.
+ Volatile, ///< Volatile-qualified string data: diagnose, do not preserve.
+ BadStorage, ///< Non-static storage duration: diagnose, do not preserve.
+ DynamicInit, ///< Not constant-initialized: diagnose, do not preserve.
+ NotStringLiteral, ///< Pointer not bound to a string literal: diagnose.
+ Preserve, ///< Supported character pointer/array: preserve in the object.
+ };
- /// Check if a variable is eligible to be treated as a loadtime comment
- /// variable (must be in the requested list and have a valid type).
- bool isLoadTimeCommentCandidateVariable(
- const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
+ /// Classify a variable whose mangled name matched the
+ /// -mloadtime-comment-vars= list.
+ LoadTimeCommentVarKind
+ classifyLoadTimeCommentVariable(const VarDecl *D) const;
- /// Return true if the mangled IR name of a Global Variable matches any entry
+ /// Return true if the mangled IR name of a Variable matches any entry
/// in LoadTimeCommentVars list.
bool matchesLoadTimeCommentVarName(
const VarDecl *VD, const std::vector<std::string> &LoadTimeCommentVars);
+
+ /// Return true if \p VD is named in -mloadtime-comment-vars= and should be
+ /// forced through the normal emission path so it can be preserved or
+ /// diagnosed. Unsupported forms (wrong type or no initializer) are left to
+ /// the usual rules.
+ /// Not const: matching a name mangles \p VD, which mutates the mangling
+ /// caches.
+ bool isForcedLoadTimeCommentVar(const VarDecl *VD);
+
+ /// Apply the -mloadtime-comment-vars= request to \p GV, whose mangled name
+ /// has already matched an entry in the list. Diagnose variables that cannot
+ /// be honored (e.g. volatile, non-static storage duration, dynamic
+ /// initialization, or a pointer not bound to a string literal); mark valid
+ /// character pointer/array definitions for preservation in the object file.
+ void handleLoadTimeCommentVariable(const VarDecl *D,
+ llvm::GlobalVariable *GV);
};
} // end namespace CodeGen
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
new file mode 100644
index 0000000000000..f9a46bd796f77
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
@@ -0,0 +1,228 @@
+// C/C++ behavior of -mloadtime-comment-vars= :
+// codegen.cpp - mangled-name matching and what gets preserved
+// storage.cpp - storage-duration and scope diagnostics
+// diag.c - volatile / non-string-literal diagnostics (C)
+// init.cpp - constant-initialization / string-literal diagnostics (C++)
+
+// RUN: rm -rf %t && split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++17 -O2 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1N1qE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE \
+// RUN: -emit-llvm -disable-llvm-passes -o - %t/codegen.cpp | FileCheck %t/codegen.cpp
+//
+// RUN: %clang_cc1 -std=c++17 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn \
+// RUN: -emit-llvm -verify -o - %t/storage.cpp | FileCheck %t/storage.cpp
+//
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr \
+// RUN: -emit-llvm -verify -o - %t/diag.c | FileCheck %t/diag.c
+//
+// RUN: %clang_cc1 -std=c++17 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,p_ind \
+// RUN: -emit-llvm -verify -o - %t/init.cpp | FileCheck %t/init.cpp
+
+//--- codegen.cpp
+// Names are matched against mangled IR symbol names.
+// C++ variables use Itanium ABI mangling; C/file-scope statics keep their
+// source name.
+//
+// Mangled names used here:
+// x -> x (file-scope, no mangling)
+// N::x -> _ZN1N1xE
+// N::q -> _ZN1N1qE
+// N::ptr -> _ZN1NL3ptrE (static, internal linkage)
+// A::x -> _ZN1A1xE
+// B::ver -> _ZN1B3verE
+// C::info -> _ZN1C4infoE (declared only, no definition — skipped)
+
+// 1. File-scope array "x" — no mangling in C++, IR name == source name.
+char x[] = "@(#) global x";
+
+namespace N {
+char x[] = "@(#) ns x";
+
+// 2. Namespace member "N::x" — mangled as _ZN1N1xE.
+char q[] = "@(#) ns q";
+
+// 3. Namespace-scope pointer initialized with a string literal.
+// _ZN1NL3ptrE (N::ptr) is internal (it is a const variable at namespace
+// scope). MustBeEmitted forces it through EmitGlobalVarDefinition.
+static const char *ptr = "@(#) ns ptr";
+} // namespace N
+
+// 4. Class static member "A::x" — mangled as _ZN1A1xE.
+struct A {
+ static const char *x;
+};
+const char *A::x = "@(#) class x";
+
+// 5. Class static member pointer initialized with a string literal.
+// _ZN1B3verE (B::ver).
+struct B {
+ static const char *ver;
+};
+const char *B::ver = "@(#) class ver";
+
+// 6. _ZN1C4infoE is in the list but C::info has no definition in this TU —
+// must be silently skipped.
+struct C { static const char *info; };
+
+// 7. Invalid type — int must not be tagged regardless of its IR name.
+int not_string = 7;
+
+void f() {}
+
+// File-scope x and namespace N::x both matched.
+// CHECK-DAG: @x = global [14 x i8] c"@(#) global x\00", align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK-DAG: @_ZN1N1xE = global [10 x i8] c"@(#) ns x\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+
+// N::q matched by mangled name _ZN1N1qE.
+// CHECK-DAG: @_ZN1N1qE = global [10 x i8] c"@(#) ns q\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+
+// A::x matched by mangled name _ZN1A1xE.
+// CHECK-DAG: @[[AX:_ZN1A1xE]] = {{.*}}global ptr @[[AXSTR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[AXSTR]] = private unnamed_addr constant [13 x i8] c"@(#) class x\00", align {{[0-9]+}}
+
+// N::ptr (_ZN1NL3ptrE) points to a string literal.
+// CHECK-DAG: @_ZN1NL3ptrE = internal global ptr @[[NPTR_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[NPTR_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) ns ptr\00", align {{[0-9]+}}
+
+// B::ver (_ZN1B3verE) points to a string literal.
+// CHECK-DAG: @_ZN1B3verE = global ptr @[[BVER_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[BVER_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) class ver\00", align {{[0-9]+}}
+
+// Invalid type must not be tagged.
+// CHECK-NOT: @not_string{{.*}}!loadtime_comment
+
+// C::info has no definition — must not appear.
+// CHECK-NOT: @_ZN1C4infoE
+
+// All six selected globals are preserved in llvm.compiler.used.
+// CHECK: @llvm.compiler.used = appending global [6 x ptr]
+// CHECK-SAME: @x
+// CHECK-SAME: @_ZN1N1xE
+// CHECK-SAME: @_ZN1N1qE
+// CHECK-SAME: @_ZN1NL3ptrE
+// CHECK-SAME: @[[AX]]
+// CHECK-SAME: @_ZN1B3verE
+// CHECK-SAME: section "llvm.metadata"
+
+//--- storage.cpp
+// Storage-duration and scope handling for -mloadtime-comment-vars=.
+//
+// To be preserved a variable must have static storage duration and be defined
+// at file, namespace, or class scope. A thread_local variable (thread storage
+// duration) is diagnosed. A function-local static has static storage duration
+// but is emitted through a different path, so it is silently ignored.
+//
+// Mangled names used here:
+// keep -> keep (namespace-scope, external linkage) -- preserved
+// N::tl -> _ZN1N2tlE (thread_local) -- diagnosed
+// stl -> _ZL3stl (static thread_local, internal) -- diagnosed
+// A::tm -> _ZN1A2tmE (thread_local static member) -- diagnosed
+// f()::fn -> _ZZ1fvE2fn (function-local static) -- ignored
+
+// Supported: namespace scope, static storage duration -> preserved.
+const char *keep = "@(#) keep";
+
+namespace N {
+// Thread storage duration -> diagnosed.
+thread_local const char *tl = "@(#) tl"; // expected-warning {{'tl' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
+} // namespace N
+
+// 'static' here only changes linkage; the storage duration is still thread.
+static thread_local const char *stl = "@(#) stl"; // expected-warning {{'stl' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
+
+struct A {
+ static thread_local const char *tm;
+};
+thread_local const char *A::tm = "@(#) tm"; // expected-warning {{'tm' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
+
+// Function-local static: static storage duration, but not emitted through the
+// global-variable path, so it is silently ignored (no diagnostic, not marked).
+void f() { static const char *fn = "@(#) fn"; (void)fn; }
+
+// Only the namespace-scope variable is preserved.
+// CHECK: @keep = {{.*}}!loadtime_comment
+// CHECK-NOT: @_ZN1N2tlE = {{.*}}!loadtime_comment
+// CHECK-NOT: @_ZL3stl = {{.*}}!loadtime_comment
+// CHECK-NOT: @_ZN1A2tmE = {{.*}}!loadtime_comment
+// CHECK-NOT: @_ZZ1fvE2fn = {{.*}}!loadtime_comment
+
+//--- diag.c
+// Variables named in -mloadtime-comment-vars= that the feature cannot honor are
+// diagnosed, while a valid const character array is still preserved.
+
+// Volatile-qualified pointer.
+char *volatile vol_ptr = "@(#) vol ptr"; // expected-warning {{'vol_ptr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// Pointer to volatile character.
+volatile char *vol_char = "@(#) vol char"; // expected-warning {{'vol_char' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// Volatile character array.
+volatile char vol_arr[] = "@(#) vol arr"; // expected-warning {{'vol_arr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// Thread-local variable: does not have static storage duration.
+__thread char *tls_ptr = "@(#) tls"; // expected-warning {{'tls_ptr' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
+
+// Pointer bound to another object (a "deferred pointer chain") rather than a
+// string literal.
+static const char target[] = "@(#) target";
+const char *ind_ptr = target; // expected-warning {{pointer 'ind_ptr' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+
+// A const character array is a valid form and is preserved.
+const char const_arr[] = "@(#) const arr";
+
+// The diagnosed variables are still emitted, but without the metadata that
+// marks them for preservation.
+// CHECK-NOT: @vol_ptr = {{.*}}!loadtime_comment
+// CHECK-NOT: @vol_char = {{.*}}!loadtime_comment
+// CHECK-NOT: @vol_arr = {{.*}}!loadtime_comment
+// CHECK-NOT: @tls_ptr = {{.*}}!loadtime_comment
+// CHECK-NOT: @ind_ptr = {{.*}}!loadtime_comment
+// CHECK: @const_arr = {{.*}}constant {{.*}}!loadtime_comment
+
+// Only const_arr is kept alive. The diagnosed variables -- including the
+// deferred pointer ind_ptr -- are absent from llvm.compiler.used, so they are
+// dropped from the final binary.
+// CHECK: @llvm.compiler.used = appending global [1 x ptr]
+// CHECK-SAME: @const_arr
+// CHECK-SAME: section "llvm.metadata"
+
+//--- init.cpp
+// Initializer-form requirements for -mloadtime-comment-vars=:
+// * the variable must be constant-initialized (no dynamic initialization), so
+// that the string is present in the object at load time, and
+// * the pointer form must be bound directly to a string literal.
+
+const char *make();
+
+// Supported: a pointer bound to a string literal, and an array initialized
+// from a string literal.
+const char *p_ok = "@(#) p_ok";
+char arr_ok[] = "@(#) arr_ok";
+
+// A constant character array, referenced by a pointer below.
+const char src[] = "@(#) src";
+
+// Dynamic initialization: the value is assigned by a startup constructor, so
+// the object would not contain the intended string.
+const char *p_dyn = make(); // expected-warning {{'p_dyn' named in '-mloadtime-comment-vars=' is not constant-initialized and will not be preserved}}
+
+// Constant-initialized, but the pointer is bound to another global (a "deferred
+// pointer chain") rather than a string literal.
+const char *p_ind = src; // expected-warning {{pointer 'p_ind' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+
+// CHECK: @p_ok = {{.*}}!loadtime_comment
+// CHECK: @arr_ok = {{.*}}!loadtime_comment
+// CHECK-NOT: @p_dyn = {{.*}}!loadtime_comment
+// CHECK-NOT: @p_ind = {{.*}}!loadtime_comment
+
+// Only the two valid forms are kept alive. The dynamically initialized pointer
+// and the deferred (indirect) pointer are absent from llvm.compiler.used, so
+// they are dropped from the final binary rather than preserved.
+// CHECK: @llvm.compiler.used = appending global [2 x ptr]
+// CHECK-SAME: @p_ok
+// CHECK-SAME: @arr_ok
+// CHECK-SAME: section "llvm.metadata"
diff --git a/clang/test/CodeGen/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
similarity index 80%
rename from clang/test/CodeGen/loadtime-comment-vars.c
rename to clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index 057c39f4f8380..a394637471a48 100644
--- a/clang/test/CodeGen/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
-// RUN: %clang_cc1 -O2 -triple x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=NONAIX
+// RUN: %clang_cc1 -O2 -triple x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=LINUX
// 1. String pointer
static char *sccsid = "@(#) sccsid Version 1.0";
@@ -21,10 +21,9 @@ struct build_info {
int minor;
} static build_data = {1, 0};
-// 6. Deferred: pointer whose initializer references another static global.
-// Both the pointer AND the string it points to must be emitted.
-static const char dummy[] = "dummy copyright deferred";
-static const char *same_copyright = dummy;
+// 6. Pointer initialized with a string literal; forced into emission even
+// though it is never referenced.
+static const char *same_copyright = "@(#) same copyright";
// 7. Variable already referenced (eager emission path)
static char *active = "@(#) active string";
@@ -40,8 +39,8 @@ void foo() {}
// CHECK-DAG: @sccsid = internal global ptr @[[SCCSID_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[SCCSID_STR]] = private unnamed_addr constant [24 x i8] c"@(#) sccsid Version 1.0\00", align {{[0-9]+}}
// CHECK-DAG: @version = internal global [27 x i8] c"@(#) Copyright Version 2.0\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @same_copyright = internal global ptr @dummy, align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @dummy = internal constant [25 x i8] c"dummy copyright deferred\00"
+// CHECK-DAG: @same_copyright = internal global ptr @[[SC_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[SC_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) same copyright\00", align {{[0-9]+}}
// CHECK: @llvm.compiler.used = appending global [4 x ptr]
// CHECK-SAME: ptr @sccsid
// CHECK-SAME: ptr @version
@@ -55,7 +54,7 @@ void foo() {}
// CHECK-NOT: @build_data
// CHECK-NOT: @not_defined_here
-// NONAIX-NOT: loadtime_comment
-// NONAIX-NOT: @sccsid
-// NONAIX-NOT: @version
+// LINUX-NOT: loadtime_comment
+// LINUX-NOT: @sccsid
+// LINUX-NOT: @version
diff --git a/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
deleted file mode 100644
index 01a19b96f8ba1..0000000000000
--- a/clang/test/CodeGen/loadtime-comment-vars-cxx.cpp
+++ /dev/null
@@ -1,93 +0,0 @@
-// Names are matched against mangled IR symbol names.
-// C++ variables use Itanium ABI mangling; C/file-scope statics keep their
-// source name.
-//
-// Mangled names used here:
-// x -> x (file-scope, no mangling)
-// N::x -> _ZN1N1xE
-// N::q -> _ZN1N1qE
-// N::ptr -> _ZN1NL3ptrE (static, internal linkage)
-// A::x -> _ZN1A1xE
-// B::ver -> _ZN1B3verE
-// C::info -> _ZN1C4infoE (declared only, no definition — skipped)
-
-// RUN: %clang_cc1 -std=c++17 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1N1qE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE \
-// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
-
-// 1. File-scope array "x" — no mangling in C++, IR name == source name.
-char x[] = "@(#) global x";
-
-namespace N {
-char x[] = "@(#) ns x";
-
-// 2. Namespace member "N::x" — mangled as _ZN1N1xE.
-char q[] = "@(#) ns q";
-
-// 3. Deferred pointer-chain inside a namespace.
-// _ZN1NL3ptrE (N::ptr) points to _ZN1NL4baseE (N::base, another static).
-// MustBeEmitted forces N::ptr through EmitGlobalVarDefinition; the
-// initializer reference to N::base causes N::base to be emitted too.
-static const char base[] = "base deferred ns";
-static const char *ptr = base;
-} // namespace N
-
-// 4. Class static member "A::x" — mangled as _ZN1A1xE.
-struct A {
- static const char *x;
-};
-const char *A::x = "@(#) class x";
-
-// 5. Deferred pointer-chain for a class static member.
-// _ZN1B3verE (B::ver) points to _ZL6base_b.
-struct B {
- static const char *ver;
-};
-static const char base_b[] = "base for B::ver";
-const char *B::ver = base_b;
-
-// 6. _ZN1C4infoE is in the list but C::info has no definition in this TU —
-// must be silently skipped.
-struct C { static const char *info; };
-
-// 7. Invalid type — int must not be tagged regardless of its IR name.
-int not_string = 7;
-
-void f() {}
-
-// --- Checks ----------------------------------------------------------------
-
-// File-scope x and namespace N::x both matched.
-// CHECK-DAG: @x = global [14 x i8] c"@(#) global x\00", align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
-// CHECK-DAG: @_ZN1N1xE = global [10 x i8] c"@(#) ns x\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-
-// N::q matched by mangled name _ZN1N1qE.
-// CHECK-DAG: @_ZN1N1qE = global [10 x i8] c"@(#) ns q\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-
-// A::x matched by mangled name _ZN1A1xE.
-// CHECK-DAG: @[[AX:_ZN1A1xE]] = {{.*}}global ptr @[[AXSTR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @[[AXSTR]] = private unnamed_addr constant [13 x i8] c"@(#) class x\00", align {{[0-9]+}}
-
-// Deferred: N::ptr (_ZN1NL3ptrE) points to N::base (_ZN1NL4baseE).
-// CHECK-DAG: @_ZN1NL3ptrE = internal global ptr @_ZN1NL4baseE, align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @_ZN1NL4baseE = internal constant [17 x i8] c"base deferred ns\00", align {{[0-9]+}}
-
-// Deferred: B::ver (_ZN1B3verE) points to base_b (_ZL6base_b).
-// CHECK-DAG: @_ZN1B3verE = global ptr @_ZL6base_b, align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @_ZL6base_b = internal constant [16 x i8] c"base for B::ver\00", align {{[0-9]+}}
-
-// Invalid type must not be tagged.
-// CHECK-NOT: @not_string{{.*}}!loadtime_comment
-
-// C::info has no definition — must not appear.
-// CHECK-NOT: @_ZN1C4infoE
-
-// All six selected globals are preserved in llvm.compiler.used.
-// CHECK: @llvm.compiler.used = appending global [6 x ptr]
-// CHECK-SAME: @x
-// CHECK-SAME: @_ZN1N1xE
-// CHECK-SAME: @_ZN1N1qE
-// CHECK-SAME: @_ZN1NL3ptrE
-// CHECK-SAME: @[[AX]]
-// CHECK-SAME: @_ZN1B3verE
-// CHECK-SAME: section "llvm.metadata"
>From d88b98c802110c4dd82de6d1fbe60bb3f7d24c0f Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 17 Jul 2026 19:43:29 +0530
Subject: [PATCH 6/7] Add list-parsing test cases for -mloadtime-comment-vars
---
.../PowerPC/loadtime-comment-vars-cxx.cpp | 33 +++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
index f9a46bd796f77..bea30c132f23a 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
@@ -3,6 +3,7 @@
// storage.cpp - storage-duration and scope diagnostics
// diag.c - volatile / non-string-literal diagnostics (C)
// init.cpp - constant-initialization / string-literal diagnostics (C++)
+// list.c - list parsing: whitespace after a comma, repeated names
// RUN: rm -rf %t && split-file %s %t
//
@@ -21,6 +22,14 @@
// RUN: %clang_cc1 -std=c++17 -triple powerpc64-ibm-aix \
// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,p_ind \
// RUN: -emit-llvm -verify -o - %t/init.cpp | FileCheck %t/init.cpp
+//
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
+// RUN: "-mloadtime-comment-vars=foo, bar" \
+// RUN: -emit-llvm -o - %t/list.c | FileCheck %t/list.c --check-prefix=SPACE
+//
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=foo,foo \
+// RUN: -emit-llvm -o - %t/list.c | FileCheck %t/list.c --check-prefix=DUP
//--- codegen.cpp
// Names are matched against mangled IR symbol names.
@@ -226,3 +235,27 @@ const char *p_ind = src; // expected-warning {{pointer 'p_ind' named in '-mloadt
// CHECK-SAME: @p_ok
// CHECK-SAME: @arr_ok
// CHECK-SAME: section "llvm.metadata"
+
+//--- list.c
+// List-parsing edge cases.
+//
+// "foo, bar": the list is split at commas without trimming whitespace, so the
+// second entry is " bar", which matches no mangled name. Like any other
+// unrecognised name it is silently ignored: bar is emitted normally but is
+// not preserved.
+//
+// "foo,foo": a name repeated in the list preserves the variable once; the
+// duplicate entry has no additional effect.
+
+char foo[] = "@(#) foo";
+char bar[] = "@(#) bar";
+
+void f() {}
+
+// SPACE-DAG: @foo = global [9 x i8] c"@(#) foo\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// SPACE-DAG: @bar = global [9 x i8] c"@(#) bar\00", align {{[0-9]+}}{{$}}
+// SPACE-DAG: @llvm.compiler.used = appending global [1 x ptr] [ptr @foo], section "llvm.metadata"
+
+// DUP-DAG: @foo = global [9 x i8] c"@(#) foo\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// DUP-DAG: @bar = global [9 x i8] c"@(#) bar\00", align {{[0-9]+}}{{$}}
+// DUP-DAG: @llvm.compiler.used = appending global [1 x ptr] [ptr @foo], section "llvm.metadata"
>From 94e44f5c69de3bb837b821d8ed29e2588c43b7a1 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 17 Jul 2026 19:52:18 +0530
Subject: [PATCH 7/7] nit: CodeGenModule.cpp new line deletion
---
clang/lib/CodeGen/CodeGenModule.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index b223423d41d58..c5f84e9ef690b 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1132,7 +1132,6 @@ void CodeGenModule::Release() {
Module *Primary = getContext().getCurrentNamedModule();
if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
EmitModuleInitializers(Primary);
-
EmitDeferred();
DeferredDecls.insert_range(EmittedDeferredDecls);
EmittedDeferredDecls.clear();
More information about the cfe-commits
mailing list