[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
Tue Oct 6 02:24:14 PDT 2026
https://github.com/tonykuttai updated https://github.com/llvm/llvm-project/pull/187986
>From 40b600d82a676f031bb4800fbf9953bf550ead94 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 01/31] [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 bd47b18da2481..aeea31eacff43 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6555,6 +6555,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.
+
(langext-evaluating-object-size)=
## Evaluating Object Size
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 17f367bc02607..627f035da6a4f 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -361,6 +361,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 f7e22ee84a9f4..abc7753beddf5 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4943,6 +4943,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 b7bd5744bcb09..d6f1ad9adcb01 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1161,6 +1161,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();
@@ -1938,6 +1945,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());
@@ -4488,6 +4498,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 1f5ecf734c528..ff182becc11d1 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2219,6 +2219,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 ab852bf0e0043..9ec33d2894a6d 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6401,6 +6401,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 7c64fa5b273e1f6d3c8b596afa80fc5e7f22eda8 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 02/31] [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 aeea31eacff43..21f26e6e462b3 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6559,8 +6559,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
@@ -6572,6 +6573,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 d6f1ad9adcb01..f4613680a8a62 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1162,12 +1162,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();
@@ -1945,9 +1939,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());
@@ -4436,7 +4427,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);
@@ -4498,23 +4494,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;
@@ -4523,88 +4515,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) {
@@ -6749,6 +6710,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 ff182becc11d1..ab3a786d364eb 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2227,16 +2227,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 ba7068838702adecb49ca200e68498ad2b7b802a 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 03/31] [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 21f26e6e462b3..97ee0ec66febf 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6575,15 +6575,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 f4613680a8a62..bb49717cb99ef 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4515,54 +4515,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 ab3a786d364eb..d4228d7644ff4 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2225,15 +2225,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 f797448aea23618ac05bcb0d5f18406310c2fb33 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 04/31] 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 97ee0ec66febf..7dbe72dd57284 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6560,8 +6560,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
@@ -6575,14 +6574,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
@@ -6598,7 +6593,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 9a14010917a5da819efc9a90dc80dcd057960855 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 05/31] [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/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 -------
10 files changed, 420 insertions(+), 179 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 7dbe72dd57284..1ec797e623377 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6558,9 +6558,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
@@ -6572,40 +6573,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:
@@ -6624,8 +6630,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 627f035da6a4f..e975272101bd1 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -361,7 +361,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 a10f10502a702..ca9b816991502 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 1da7698944b24..4eba7f3ef4127 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1653,6 +1653,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/Options/Options.td b/clang/include/clang/Options/Options.td
index abc7753beddf5..75e04bb97587d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4947,8 +4947,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 bb49717cb99ef..7466a128fbe53 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4428,11 +4428,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);
@@ -4494,25 +4490,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
@@ -4530,15 +4552,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) {
@@ -6686,12 +6745,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 d4228d7644ff4..e4e520ce7e3f9 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2220,19 +2220,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 d698ef97d3c47e29fa096475dc58d37fe56099cb 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 06/31] 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 6d0a4de3e5971a748d73d1a9cdda71a3e1c280d3 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 07/31] 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 7466a128fbe53..9bac6789e63a0 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1161,7 +1161,6 @@ void CodeGenModule::Release() {
Module *Primary = getContext().getCurrentNamedModule();
if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
EmitModuleInitializers(Primary);
-
EmitDeferred();
DeferredDecls.insert_range(EmittedDeferredDecls);
EmittedDeferredDecls.clear();
>From 7c738aa3b868239b129c20dd99ad543f3807c12e Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Tue, 21 Jul 2026 06:36:41 +0530
Subject: [PATCH 08/31] [Clang][AIX] Move -mloadtime-comment-vars validation to
Sema. Add Release notes.
---
clang/docs/ReleaseNotes.md | 7 +
clang/include/clang/Basic/Attr.td | 10 ++
clang/include/clang/Basic/CodeGenOptions.h | 3 -
.../clang/Basic/DiagnosticFrontendKinds.td | 17 ---
.../clang/Basic/DiagnosticSemaKinds.td | 6 +
clang/include/clang/Basic/LangOptions.h | 4 +
clang/include/clang/Options/Options.td | 2 +-
clang/lib/CodeGen/CodeGenModule.cpp | 123 +-----------------
clang/lib/CodeGen/CodeGenModule.h | 40 ------
clang/lib/Driver/ToolChains/Clang.cpp | 3 +-
clang/lib/Sema/SemaDecl.cpp | 75 +++++++++++
.../PowerPC/loadtime-comment-vars-cxx.cpp | 13 +-
clang/test/Driver/mloadtime-comment-vars.c | 5 +-
13 files changed, 126 insertions(+), 182 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ef694e1d0f5cc..181922f718a75 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -791,6 +791,13 @@ features cannot lower the translation-unit ABI level;
#### AIX Support
+- Added the `-mloadtime-comment-vars=` option, which accepts a comma-separated
+ list of mangled variable names to preserve in the final object file as
+ loadtime identifying strings. This complements `#pragma comment(copyright,
+ ...)` for codebases that embed identifying strings in source variables. A
+ named variable that cannot be preserved is diagnosed with
+ `-Wloadtime-comment-var`.
+
#### NetBSD Support
#### WebAssembly Support
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 61ef3fb612440..95de9f52a6583 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4960,6 +4960,16 @@ def LoaderUninitialized : Attr {
let SimpleHandler = 1;
}
+def LoadTimeCommentVar : InheritableAttr {
+ // This attribute has no spellings as it is only ever created implicitly,
+ // for variables named in '-mloadtime-comment-vars=' that Sema has
+ // validated for preservation as loadtime identifying strings.
+ let Spellings = [];
+ let Subjects = SubjectList<[GlobalVar]>;
+ let SemaHandler = 0;
+ let Documentation = [InternalOnly];
+}
+
def ObjCExternallyRetained : InheritableAttr {
let LangOpts = [ObjCAutoRefCount];
let Spellings = [Clang<"objc_externally_retained">];
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index e975272101bd1..17f367bc02607 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -361,9 +361,6 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// A list of linker options to embed in the object file.
std::vector<std::string> LinkerOptions;
- /// 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,
/// -fprofile-generate, and -fcs-profile-generate.
std::string InstrProfileOutput;
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index ca9b816991502..a10f10502a702 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -26,23 +26,6 @@ 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/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index a7b40a7b64c2d..663e5cc87aa39 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3862,6 +3862,12 @@ def warn_not_xl_compatible
InGroup<AIXCompat>;
def note_misaligned_member_used_here : Note<
"passing byval argument %0 with potentially incompatible alignment here">;
+def warn_loadtime_comment_var_not_preserved
+ : Warning<"%0 named in '-mloadtime-comment-vars=' %select{is "
+ "volatile-qualified|does not have static storage duration|is "
+ "not constant-initialized|is not initialized with a string "
+ "literal}1 and will not be preserved">,
+ InGroup<LoadtimeCommentVar>;
def warn_redeclaration_without_attribute_prev_attribute_ignored : Warning<
"%q0 redeclared without %1 attribute: previous %1 ignored">,
InGroup<MicrosoftInconsistentDllImport>;
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 7539e000d03f9..874011bea48a1 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -547,6 +547,10 @@ class LangOptions : public LangOptionsBase {
/// A list of all -fno-builtin-* function names (e.g., memset).
std::vector<std::string> NoBuiltinFuncs;
+ /// List of mangled variable names to preserve as loadtime comment
+ /// variables.
+ std::vector<std::string> LoadTimeCommentVars;
+
/// A prefix map for __FILE__, __BASE_FILE__ and __builtin_FILE().
std::map<std::string, std::string, std::greater<std::string>> MacroPrefixMap;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 75e04bb97587d..c4dc2bfc6d61d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4949,7 +4949,7 @@ def mloadtime_comment_vars_EQ
Visibility<[ClangOption, CC1Option]>,
HelpText<"Comma-separated list of mangled variable names to preserve as "
"loadtime identifying strings">,
- MarshallingInfoStringVector<CodeGenOpts<"LoadTimeCommentVars">>;
+ MarshallingInfoStringVector<LangOpts<"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 9bac6789e63a0..23628ff0b9870 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4427,7 +4427,7 @@ bool CodeGenModule::MustBeEmitted(const ValueDecl *Global) {
VD->getStorageDuration() == SD_Thread)) ||
(CodeGenOpts.KeepStaticConsts && VD->getStorageDuration() == SD_Static &&
VD->getType().isConstQualified()) ||
- isForcedLoadTimeCommentVar(VD)))
+ VD->hasAttr<LoadTimeCommentVarAttr>()))
return true;
return getContext().DeclMustBeEmitted(Global);
@@ -4489,116 +4489,6 @@ bool CodeGenModule::MayBeEmittedEagerly(const ValueDecl *Global) {
return true;
}
-/// 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();
- 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
-/// LoadTimeCommentVars list. Users supply the mangled name as it appears in the
-/// object file.
-///
-/// 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) {
- if (!VD)
- return false;
- StringRef MangledName = getMangledName(GlobalDecl(VD));
- return llvm::is_contained(LoadTimeCommentVars, MangledName);
-}
-
-/// 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) {
StringRef Name = getMangledName(GD);
@@ -6741,11 +6631,12 @@ void CodeGenModule::EmitGlobalVarDefinition(const VarDecl *D,
if (D->hasAttr<AnnotateAttr>())
AddGlobalAnnotations(D, GV);
- if (getTriple().isOSAIX()) {
- const auto &LoadTimeCommentVars = getCodeGenOpts().LoadTimeCommentVars;
- if (!LoadTimeCommentVars.empty() &&
- matchesLoadTimeCommentVarName(D, LoadTimeCommentVars))
- handleLoadTimeCommentVariable(D, GV);
+ // Variables Sema validated for '-mloadtime-comment-vars=' are marked for
+ // LowerCommentStringPass and kept alive.
+ if (D->hasAttr<LoadTimeCommentVarAttr>()) {
+ GV->setMetadata("loadtime_comment",
+ llvm::MDNode::get(getLLVMContext(), {}));
+ llvm::appendToCompilerUsed(getModule(), {GV});
}
// Set the llvm linkage type as appropriate.
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index e4e520ce7e3f9..1f5ecf734c528 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2219,46 +2219,6 @@ class CodeGenModule : public CodeGenTypeCache {
/// Emit deactivation symbols for any PFP fields whose offset is taken with
/// offsetof.
void emitPFPFieldsWithEvaluatedOffset();
-
- /// 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.
- };
-
- /// 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 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/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 9ec33d2894a6d..18a39713d20d6 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6401,7 +6401,8 @@ 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.
+ // Forward the loadtime-comment vars option to cc1 only on targets that
+ // support it.
if (Arg *A = Args.getLastArg(options::OPT_mloadtime_comment_vars_EQ)) {
if (Triple.isOSAIX())
A->render(Args, CmdArgs);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a9047f61a8bf5..73a0544691c36 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -24,6 +24,7 @@
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
+#include "clang/AST/Mangle.h"
#include "clang/AST/MangleNumberingContext.h"
#include "clang/AST/NonTrivialTypeVisitor.h"
#include "clang/AST/Randstruct.h"
@@ -15460,6 +15461,73 @@ void Sema::CheckThreadLocalForLargeAlignment(VarDecl *VD) {
}
}
+/// Process a variable definition whose mangled name may be listed in
+/// '-mloadtime-comment-vars=': attach an implicit attribute to supported
+/// string variables so CodeGen preserves them as loadtime identifying
+/// strings, and warn when a named variable cannot be preserved.
+static void checkLoadTimeCommentVar(Sema &S, VarDecl *VD) {
+ // Only variables defined at file, namespace, or class scope participate;
+ // anything else (e.g. a function-local static) is silently ignored. A
+ // template has no object-file symbol of its own, only its instantiations
+ // do, so template patterns are ignored as well.
+ if (!VD->isFileVarDecl() && !VD->isStaticDataMember())
+ return;
+ if (VD->isTemplated())
+ return;
+ if (VD->isThisDeclarationADefinition(S.Context) != VarDecl::Definition)
+ return;
+
+ // Only character pointers/arrays with an initializer are supported; a
+ // matched variable of any other form (int, struct, no initializer, ...) is
+ // silently ignored.
+ QualType Ty = VD->getType();
+ const PointerType *PT = Ty->getAs<PointerType>();
+ const ArrayType *AT = PT ? nullptr : S.Context.getAsArrayType(Ty);
+ QualType Pointee = PT ? PT->getPointeeType()
+ : AT ? AT->getElementType()
+ : QualType();
+ if (Pointee.isNull() || !Pointee->isAnyCharacterType() || !VD->hasInit())
+ return;
+
+ // Names are matched against the mangled name, as it appears in the object
+ // file. For plain C file-scope variables this is the source identifier; for
+ // C++ variables it is the mangled symbol.
+ if (!llvm::is_contained(S.getLangOpts().LoadTimeCommentVars,
+ ASTNameGenerator(S.Context).getName(VD)))
+ return;
+
+ // Indices of the %select in warn_loadtime_comment_var_not_preserved.
+ enum { Volatile, BadStorage, DynamicInit, NotStringLiteral };
+ int Reason = -1;
+ if (VD->getStorageDuration() != SD_Static)
+ // The string must have static storage duration; a thread-local variable
+ // is not preserved.
+ Reason = BadStorage;
+ else if (Ty.isVolatileQualified() || Pointee.isVolatileQualified())
+ // A volatile string has no stable value to embed, whether the variable
+ // itself or the character it refers to is volatile-qualified.
+ Reason = Volatile;
+ else if (!VD->hasConstantInitialization())
+ // 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.
+ Reason = DynamicInit;
+ else if (PT && !isa<StringLiteral>(VD->getInit()->IgnoreParenImpCasts()))
+ // 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.
+ Reason = NotStringLiteral;
+
+ if (Reason >= 0) {
+ S.Diag(VD->getLocation(), diag::warn_loadtime_comment_var_not_preserved)
+ << VD << Reason;
+ return;
+ }
+
+ VD->addAttr(
+ LoadTimeCommentVarAttr::CreateImplicit(S.Context, VD->getLocation()));
+}
+
void Sema::FinalizeDeclaration(Decl *ThisDecl) {
// Note that we are no longer parsing the initializer for this declaration.
ParsingInitForAutoVars.erase(ThisDecl);
@@ -15576,6 +15644,13 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
}
}
+ // Validate variables named in '-mloadtime-comment-vars=': supported string
+ // variables get an implicit attribute that CodeGen uses to preserve them;
+ // named variables that cannot be preserved are diagnosed.
+ if (!getLangOpts().LoadTimeCommentVars.empty() && !VD->isInvalidDecl() &&
+ Context.getTargetInfo().getTriple().isOSAIX())
+ checkLoadTimeCommentVar(*this, VD);
+
const DeclContext *DC = VD->getDeclContext();
// If there's a #pragma GCC visibility in scope, and this isn't a class
// member, set the visibility of this variable.
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
index bea30c132f23a..e2989f8fbd112 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
@@ -23,6 +23,15 @@
// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,p_ind \
// RUN: -emit-llvm -verify -o - %t/init.cpp | FileCheck %t/init.cpp
//
+// The diagnostics are produced by Sema, so they are emitted even when no code
+// is generated.
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr \
+// RUN: -fsyntax-only -verify %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: -fsyntax-only -verify %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
@@ -178,7 +187,7 @@ __thread char *tls_ptr = "@(#) tls"; // expected-warning {{'tls_ptr' named in '-
// 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}}
+const char *ind_ptr = target; // expected-warning {{'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";
@@ -221,7 +230,7 @@ const char *p_dyn = make(); // expected-warning {{'p_dyn' named in '-mloadtime-c
// 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}}
+const char *p_ind = src; // expected-warning {{'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
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
index 77d77e2552376..68f01c39804e4 100644
--- a/clang/test/Driver/mloadtime-comment-vars.c
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -1,11 +1,12 @@
// 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
-// Verify the option is forwarded verbatim to cc1 on AIX.
+// Verify the option is forwarded verbatim to cc1 for a supported target.
// CHECK: "-cc1"
// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
-// Verify a warning is emitted and the option is NOT forwarded on non-AIX targets.
+// Verify a warning is emitted and the option is NOT forwarded for an
+// unsupported target.
// 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 9efa0663547f0de7f52b2fbe7803f942536cdd53 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 30 Jul 2026 07:22:54 +0530
Subject: [PATCH 09/31] [Clang][AIX] Restrict -mloadtime-comment-vars to
file/namespace scope
Support only file- and namespace-scope variables. Name-matched static
data members, variable template specializations (explicit ones
included), and function-local statics are now diagnosed with
-Wloadtime-comment-var instead of being silently ignored. Implicit
instantiations are diagnosed via the template-instantiation path, once
per instantiating TU. Automatic locals have no symbol to match and
remain out of scope.
---
clang/docs/LanguageExtensions.md | 44 ++-
.../clang/Basic/DiagnosticSemaKinds.td | 4 +-
clang/include/clang/Basic/LangOptions.h | 3 +
clang/include/clang/Sema/Sema.h | 5 +
clang/lib/Basic/LangOptions.cpp | 4 +
clang/lib/Sema/SemaDecl.cpp | 70 +++--
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 4 +
.../PowerPC/loadtime-comment-vars-cxx.cpp | 270 ------------------
.../CodeGen/PowerPC/loadtime-comment-vars.c | 105 +++++--
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 234 +++++++++++++++
clang/test/Sema/loadtime-comment-vars.c | 40 +++
clang/test/Sema/loadtime-comment-vars.cpp | 117 ++++++++
.../lower-comment-string.ll | 30 +-
13 files changed, 584 insertions(+), 346 deletions(-)
delete mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
create mode 100644 clang/test/Sema/loadtime-comment-vars.c
create mode 100644 clang/test/Sema/loadtime-comment-vars.cpp
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 1ec797e623377..a937a3beb1649 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6575,16 +6575,17 @@ Syntax:
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:
+Itanium C++ ABI, so a namespace-scoped, class-scoped, or internal-linkage
+variable (for example, a file-scope `static`) must be named using its mangled
+form:
```c++
-namespace N { char sccsid[] = "@(#) MyApp Version 1.0"; } // N::sccsid -> _ZN1N6sccsidE
-const char *App::version = "@(#) Built 2026-06-25"; // App::version -> _ZN3App7versionE
+namespace N { char sccsid[] = "@(#) MyApp Version 1.0"; } // N::sccsid -> _ZN1N6sccsidE
+static char build[] = "@(#) Level 42"; // build -> _ZL5build
```
```console
--mloadtime-comment-vars=_ZN1N6sccsidE,_ZN3App7versionE
+-mloadtime-comment-vars=_ZN1N6sccsidE,_ZL5build
```
Valid variable types:
@@ -6592,26 +6593,41 @@ Valid variable types:
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).
+- It must be defined at file or namespace scope. A name-matched function-local
+ `static` variable, static data member, or variable template specialization
+ is not supported and is diagnosed.
- Its type must be a character pointer (`char *`, `const char *`) or a
- character array (`char[]`, `const char[]`).
+ character array (`char[]`, `const char[]`). The character type must be plain
+ `char`: variables of `signed char`, `unsigned char`, and the wide and
+ Unicode character types (`wchar_t`, `char8_t`, `char16_t`, `char32_t`) are
+ not matched.
- 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.
+ -- even a constant one, such as another character array or the result of a
+ `constexpr` function returning the address of an external array -- does not
+ itself carry the identifying string and is not preserved. The expected
+ behavior is that the identifying string is present in the object file
+ compiled from the defining translation unit itself, not merely in the final
+ linked output.
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.
+diagnosed with a warning and is not preserved. The same applies to name-matched
+variables of unsupported kinds: function-local `static` variables, static data
+members, and variable template specializations (implicit specializations are
+diagnosed in each translation unit that instantiates them). Variables of an
+unsupported type -- for example, an `int` or a `struct` -- or without an
+initializer are silently skipped, as are names that are not defined in the
+translation unit.
+
+For C++20 modules, a named variable defined in a module unit is processed when
+that module unit itself is compiled to object code, and the option applies to
+that compilation.
Example:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 663e5cc87aa39..392e3d211e5f6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3866,7 +3866,9 @@ def warn_loadtime_comment_var_not_preserved
: Warning<"%0 named in '-mloadtime-comment-vars=' %select{is "
"volatile-qualified|does not have static storage duration|is "
"not constant-initialized|is not initialized with a string "
- "literal}1 and will not be preserved">,
+ "literal|is a function-local variable|is a static data member|"
+ "is a variable template specialization}1 and will not be "
+ "preserved">,
InGroup<LoadtimeCommentVar>;
def warn_redeclaration_without_attribute_prev_attribute_ignored : Warning<
"%q0 redeclared without %1 attribute: previous %1 ignored">,
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 874011bea48a1..1322695c3c899 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -705,6 +705,9 @@ class LangOptions : public LangOptionsBase {
/// builtin because a -fno-builtin-* option has been specified?
bool isNoBuiltinFunc(StringRef Name) const;
+ /// Returns true if \p MangledName is listed in -mloadtime-comment-vars=.
+ bool isLoadTimeCommentVar(StringRef MangledName) const;
+
/// True if any ObjC types may have non-trivial lifetime qualifiers.
bool allowsNonTrivialObjCLifetimeQualifiers() const {
return ObjCAutoRefCount || ObjCWeak;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4ff4c669a6b70..aa6622fc4c172 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4193,6 +4193,11 @@ class Sema final : public SemaBase {
/// FinalizeDeclaration - called by ParseDeclarationAfterDeclarator to perform
/// any semantic actions necessary after any initializer has been attached.
void FinalizeDeclaration(Decl *D);
+
+ /// Process a variable definition against '-mloadtime-comment-vars='.
+ /// Exposed for instantiated variable definitions, which do not pass
+ /// through FinalizeDeclaration.
+ void ProcessLoadTimeCommentVar(VarDecl *VD);
DeclGroupPtrTy FinalizeDeclaratorGroup(Scope *S, const DeclSpec &DS,
ArrayRef<Decl *> Group);
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 69b9176801aea..f61df3483c483 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -57,6 +57,10 @@ bool LangOptions::isNoBuiltinFunc(StringRef FuncName) const {
return false;
}
+bool LangOptions::isLoadTimeCommentVar(StringRef MangledName) const {
+ return llvm::is_contained(LoadTimeCommentVars, MangledName);
+}
+
VersionTuple LangOptions::getOpenCLVersionTuple() const {
const int Ver = OpenCLCPlusPlus ? OpenCLCPlusPlusVersion : OpenCLVersion;
if (OpenCLCPlusPlus && Ver != 100)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 73a0544691c36..0959ee29b2069 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15465,47 +15465,74 @@ void Sema::CheckThreadLocalForLargeAlignment(VarDecl *VD) {
/// '-mloadtime-comment-vars=': attach an implicit attribute to supported
/// string variables so CodeGen preserves them as loadtime identifying
/// strings, and warn when a named variable cannot be preserved.
-static void checkLoadTimeCommentVar(Sema &S, VarDecl *VD) {
- // Only variables defined at file, namespace, or class scope participate;
- // anything else (e.g. a function-local static) is silently ignored. A
- // template has no object-file symbol of its own, only its instantiations
- // do, so template patterns are ignored as well.
- if (!VD->isFileVarDecl() && !VD->isStaticDataMember())
+static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
+ // The driver restricts the option to AIX, but cc1 can be invoked directly
+ // with any triple, so the target is checked here rather than inherited from
+ // driver gating.
+ if (!S.Context.getTargetInfo().getTriple().isOSAIX())
+ return;
+ // Declarations that cannot be name-matched are silently skipped: an
+ // automatic variable has no symbol of its own, and neither does a template
+ // pattern (only its specializations do, and those are processed
+ // separately). Only definitions are considered.
+ if (VD->hasLocalStorage())
return;
if (VD->isTemplated())
return;
if (VD->isThisDeclarationADefinition(S.Context) != VarDecl::Definition)
return;
- // Only character pointers/arrays with an initializer are supported; a
- // matched variable of any other form (int, struct, no initializer, ...) is
- // silently ignored.
+ // Only plain `char` pointers/arrays with an initializer are supported; a
+ // matched variable of any other form (int, struct, wide or explicitly
+ // signed/unsigned character types, no initializer, ...) is silently
+ // ignored.
QualType Ty = VD->getType();
- const PointerType *PT = Ty->getAs<PointerType>();
+ const PointerType *PT = Ty->getAsCanonical<PointerType>();
const ArrayType *AT = PT ? nullptr : S.Context.getAsArrayType(Ty);
QualType Pointee = PT ? PT->getPointeeType()
: AT ? AT->getElementType()
: QualType();
- if (Pointee.isNull() || !Pointee->isAnyCharacterType() || !VD->hasInit())
+ if (Pointee.isNull() ||
+ !S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy) ||
+ !VD->hasInit())
return;
// Names are matched against the mangled name, as it appears in the object
// file. For plain C file-scope variables this is the source identifier; for
// C++ variables it is the mangled symbol.
- if (!llvm::is_contained(S.getLangOpts().LoadTimeCommentVars,
- ASTNameGenerator(S.Context).getName(VD)))
+ if (!S.getLangOpts().isLoadTimeCommentVar(
+ ASTNameGenerator(S.Context).getName(VD)))
return;
// Indices of the %select in warn_loadtime_comment_var_not_preserved.
- enum { Volatile, BadStorage, DynamicInit, NotStringLiteral };
+ enum {
+ Volatile,
+ BadStorage,
+ DynamicInit,
+ NotStringLiteral,
+ FunctionLocal,
+ StaticDataMember,
+ TemplateSpecialization
+ };
int Reason = -1;
- if (VD->getStorageDuration() != SD_Static)
+ if (VD->isLocalVarDecl())
+ // Only file- and namespace-scope variables are supported. A name match
+ // on anything else demonstrates intent (scope participates in the
+ // mangled name), so the unsupported kinds are diagnosed rather than
+ // silently ignored.
+ Reason = FunctionLocal;
+ else if (isa<VarTemplateSpecializationDecl>(VD))
+ Reason = TemplateSpecialization;
+ else if (VD->isStaticDataMember())
+ Reason = StaticDataMember;
+ else if (VD->getStorageDuration() != SD_Static)
// The string must have static storage duration; a thread-local variable
// is not preserved.
Reason = BadStorage;
else if (Ty.isVolatileQualified() || Pointee.isVolatileQualified())
- // A volatile string has no stable value to embed, whether the variable
- // itself or the character it refers to is volatile-qualified.
+ // The intended usage does not intersect with use cases where the character
+ // array or the pointer to it is volatile-qualified; such variables are not
+ // preserved.
Reason = Volatile;
else if (!VD->hasConstantInitialization())
// The string has to be present in the object at load time. A dynamically
@@ -15528,6 +15555,10 @@ static void checkLoadTimeCommentVar(Sema &S, VarDecl *VD) {
LoadTimeCommentVarAttr::CreateImplicit(S.Context, VD->getLocation()));
}
+void Sema::ProcessLoadTimeCommentVar(VarDecl *VD) {
+ processForLoadTimeCommentVar(*this, VD);
+}
+
void Sema::FinalizeDeclaration(Decl *ThisDecl) {
// Note that we are no longer parsing the initializer for this declaration.
ParsingInitForAutoVars.erase(ThisDecl);
@@ -15647,9 +15678,8 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
// Validate variables named in '-mloadtime-comment-vars=': supported string
// variables get an implicit attribute that CodeGen uses to preserve them;
// named variables that cannot be preserved are diagnosed.
- if (!getLangOpts().LoadTimeCommentVars.empty() && !VD->isInvalidDecl() &&
- Context.getTargetInfo().getTriple().isOSAIX())
- checkLoadTimeCommentVar(*this, VD);
+ if (!getLangOpts().LoadTimeCommentVars.empty() && !VD->isInvalidDecl())
+ processForLoadTimeCommentVar(*this, VD);
const DeclContext *DC = VD->getDeclContext();
// If there's a #pragma GCC visibility in scope, and this isn't a class
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 7668b75e836e4..ec06aeb87ea79 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6846,6 +6846,10 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
OldVar->getPointOfInstantiation());
// Emit any deferred warnings for the variable's initializer
AnalysisWarnings.issueWarningsForRegisteredVarDecl(Var);
+ // Variables named in '-mloadtime-comment-vars=' are normally processed in
+ // FinalizeDeclaration, which instantiated definitions do not reach.
+ if (!getLangOpts().LoadTimeCommentVars.empty() && !Var->isInvalidDecl())
+ ProcessLoadTimeCommentVar(Var);
}
// This variable may have local implicit instantiations that need to be
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
deleted file mode 100644
index e2989f8fbd112..0000000000000
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-cxx.cpp
+++ /dev/null
@@ -1,270 +0,0 @@
-// 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++)
-// list.c - list parsing: whitespace after a comma, repeated names
-
-// 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
-//
-// The diagnostics are produced by Sema, so they are emitted even when no code
-// is generated.
-// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr \
-// RUN: -fsyntax-only -verify %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: -fsyntax-only -verify %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.
-// 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 {{'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 {{'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"
-
-//--- 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"
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index a394637471a48..e76f209e01636 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -1,21 +1,49 @@
-// 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
+// Test Behavior of -mloadtime-comment-vars= for file-scope variables, in C and in
+// the same source compiled as C++:
+// * supported forms (plain-char pointer/array with a string-literal
+// initializer) named in the list get !loadtime_comment metadata and are
+// kept alive in llvm.compiler.used, even when otherwise unreferenced;
+// * unsupported or unlisted variables are silently ignored and, when
+// unreferenced, are not emitted at all;
+// * on non-AIX targets the option has no effect;
+// * names are matched against the mangled IR name: in C that is the source
+// identifier; in C++ a file-scope static mangles (sccsid -> _ZL6sccsid).
-// 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
+// C, 32-bit and 64-bit AIX.
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c32.ll %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c64.ll %s
+// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c32.ll
+// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c64.ll
+// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-c32.ll
+// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-c64.ll
+
+// The same source as C++: internal-linkage statics are matched by mangled
+// name. (-w silences the C++ writable-strings compatibility warning for the
+// legacy `static char *` idiom.)
+// RUN: %clang_cc1 -x c++ -w -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=_ZL6sccsid,_ZL7version,_ZL12build_number,_ZL14same_copyright,_ZL6active,not_defined_here,_ZL8tdefchar,_ZL4ustr,_ZL4sstr -emit-llvm -disable-llvm-passes -o %t-cxx.ll %s
+// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar < %t-cxx.ll
+// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=_ZL9copyright -DBUILDNUM=_ZL12build_number -DBUILDDATA=_ZL10build_data -DUSTR=_ZL4ustr -DSSTR=_ZL4sstr < %t-cxx.ll
+
+// Non-AIX target: the option has no effect.
+// RUN: %clang_cc1 -O2 -triple x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version -emit-llvm -disable-llvm-passes -o %t-linux.ll %s
+// RUN: FileCheck %s --check-prefix=LINUX < %t-linux.ll
+// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-linux.ll
+// RUN: FileCheck %s --check-prefix=NOPRESERVE < %t-linux.ll
+
+// 1. A string pointer named in the list is preserved.
static char *sccsid = "@(#) sccsid Version 1.0";
-// 2. String array
+// 2. A string array named in the list is preserved.
static char version[] = "@(#) Copyright Version 2.0";
-// 3. Const string (Not in CLI list, should NOT be emitted)
+// 3. Const string (not in the list; unreferenced, so not emitted)
static const char *copyright = "@(#) Copyright 2026";
-// 4. Integer (In CLI list but invalid type, should NOT be emitted)
+// 4. Integer (in the list but unsupported type; not emitted)
static int build_number = 12345;
-// 5. Struct (not in CLI list and invalid type, NOT emitted)
+// 5. Struct (not in the list and unsupported type; not emitted)
struct build_info {
int major;
int minor;
@@ -32,29 +60,54 @@ void bar() { (void)active; }
// 8. Variable listed but only declared (extern)
extern char *not_defined_here;
+// 9. A typedef of plain char is looked through and matches like plain char.
+typedef char CHAR;
+static CHAR *tdefchar = "@(#) typedef char";
+
+// 10. These are listed, but their element type is not plain char, so they
+// are silently ignored and not emitted.
+static unsigned char ustr[] = "@(#) unsigned char string";
+static signed char sstr[] = "@(#) signed char string";
+
void foo() {}
-// CHECK-DAG: @[[ACTIVE:active]] = internal global ptr @[[ACTIVE_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD:[0-9]+]]
+// Listed, supported variables carry the metadata and stay alive.
+// CHECK-DAG: @[[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]] = 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 @[[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
-// CHECK-SAME: ptr @same_copyright
-// CHECK-SAME: ptr @active
+// CHECK-DAG: @[[VERSION]] = internal global [27 x i8] c"@(#) Copyright Version 2.0\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[SAME]] = internal global ptr @[[SAME_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[SAME_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) same copyright\00", align {{[0-9]+}}
+// CHECK-DAG: @[[TYPEDEFCHAR]] = internal global ptr @[[TYPEDEFCHAR_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[TYPEDEFCHAR_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) typedef char\00", align {{[0-9]+}}
+
+// CHECK: @llvm.compiler.used = appending global [5 x ptr]
+// CHECK-SAME: ptr @[[SCCSID]]
+// CHECK-SAME: ptr @[[VERSION]]
+// CHECK-SAME: ptr @[[SAME]]
+// CHECK-SAME: ptr @[[ACTIVE]]
+// CHECK-SAME: ptr @[[TYPEDEFCHAR]]
// 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
+// The unlisted const string, the unsupported-type variables (including the
+// listed signed/unsigned char strings, whose element type is not plain char),
+// and the extern declaration are not emitted in any configuration.
+// NOEMIT-NOT: @[[COPYRIGHT]]
+// NOEMIT-NOT: @[[BUILDNUM]]
+// NOEMIT-NOT: @[[BUILDDATA]]
+// NOEMIT-NOT: @[[USTR]]
+// NOEMIT-NOT: @[[SSTR]]
+// NOEMIT-NOT: @not_defined_here
-// LINUX-NOT: loadtime_comment
-// LINUX-NOT: @sccsid
-// LINUX-NOT: @version
+// On non-AIX targets a referenced variable is still emitted normally, just
+// not preserved.
+// LINUX: @active = internal global ptr
+// LINUX: define {{.*}}void @bar
+// On non-AIX targets nothing is preserved: no metadata, and the unreferenced
+// statics -- listed or not -- are not emitted.
+// NOPRESERVE-NOT: loadtime_comment
+// NOPRESERVE-NOT: @sccsid
+// NOPRESERVE-NOT: @version
+// NOPRESERVE-NOT: @same_copyright
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
new file mode 100644
index 0000000000000..1f40b9e093bc1
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -0,0 +1,234 @@
+// Test -mloadtime-comment-vars= IR output for C++ on AIX. Three scenarios
+// are covered, each with its own set of named variables and check prefix:
+//
+// CHECK — mangled-name matching: file- and namespace-scope variables
+// receive !loadtime_comment metadata and are added to
+// llvm.compiler.used; name-matched static data members are
+// diagnosed and left unpreserved (still emitted as ordinary
+// definitions). A second pass (NOEMIT) over the same output
+// proves that listed variables of non-plain-char element type
+// (wchar_t, char16_t) are silently ignored and not emitted.
+//
+// STORAGE — storage-duration filtering: thread_local variables are
+// diagnosed by Sema and receive no metadata; a function-local
+// static is diagnosed and receives no metadata.
+//
+// SPACE/DUP — list-parsing edge cases: a name with a leading space matches
+// nothing; a duplicate name preserves the variable exactly once.
+//
+// Names used in the matching scenario:
+//
+// Source IR symbol Expected treatment
+// ------ --------- ------------------
+// x x preserved (no mangling at C++ file scope)
+// N::x _ZN1N1xE preserved
+// N::ptr _ZN1NL3ptrE preserved ('static' gives internal linkage)
+// A::x _ZN1A1xE static data member: diagnosed, unpreserved
+// B::ver _ZN1B3verE static data member: diagnosed, unpreserved
+// C::info _ZN1C4infoE no definition in this TU: skipped
+// wstr _ZL4wstr wchar_t element type: ignored, not emitted
+// u16str _ZL6u16str char16_t element type: ignored, not emitted
+// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
+// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
+// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
+//
+// Names used in the storage scenario (namespaces and structs are renamed to
+// avoid redefinition against the matching-scenario symbols):
+//
+// Source IR symbol Expected treatment
+// ------ --------- ------------------
+// keep keep preserved (file scope, static duration)
+// S::tl _ZN1S2tlE thread_local: diagnosed, no metadata
+// stl _ZL3stl static thread_local: diagnosed, no metadata
+// T::tm _ZN1T2tmE thread_local static data member: diagnosed
+// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
+
+// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,_ZL4wstr,_ZL6u16str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl \
+// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
+// RUN: FileCheck %s < %t.ll
+// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=keep,_ZN1S2tlE,_ZL3stl,_ZN1T2tmE,_ZZ1gvE2fn \
+// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=STORAGE
+
+// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
+// RUN: "-mloadtime-comment-vars=foo, bar" \
+// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=SPACE
+
+// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=foo,foo \
+// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=DUP
+
+// ===========================================================================
+// Mangled-name matching
+// ===========================================================================
+
+// 1. A file-scope array is not mangled in C++ (the IR name equals the
+// source name) and is preserved.
+char x[] = "@(#) global x";
+
+namespace N {
+// 2. The namespace member N::x is matched by its mangled name _ZN1N1xE and
+// is preserved.
+char x[] = "@(#) ns x";
+
+// 3. A namespace-scope pointer initialized with a string literal is
+// preserved. The 'static' gives it internal linkage, so it mangles as
+// _ZN1NL3ptrE.
+static const char *ptr = "@(#) ns ptr";
+} // namespace N
+
+// 4. The static data member A::x (mangled _ZN1A1xE) is not supported: Sema
+// diagnoses it and it gets no metadata and no compiler.used entry, though
+// it is still emitted normally as an ordinary external definition.
+struct A {
+ static const char *x;
+};
+const char *A::x = "@(#) class x";
+
+// 5. The static data member B::ver receives the same treatment as A::x.
+struct B {
+ static const char *ver;
+};
+const char *B::ver = "@(#) class ver";
+
+// 6. C::info is in the list but has no definition in this translation unit,
+// so it is silently skipped.
+struct C { static const char *info; };
+
+// 7. An int has an unsupported type and must not be tagged, even though its
+// IR name matches a listed name.
+int not_string = 7;
+
+// 8. These are listed, but their element type is not plain char, so they are
+// silently ignored and, being unreferenced internal-linkage statics, not
+// emitted at all.
+static wchar_t wstr[] = L"@(#) wide";
+static char16_t u16str[] = u"@(#) u16";
+
+// 9. Eligible C++ declaration forms. Uses of these constant-fold, so without
+// the option none of them would be emitted at all — the forced emission is
+// what materializes the string in the object.
+static constexpr const char *sccsid_ce = "@(#) constexpr";
+constinit const char *sccsid_ci = "@(#) constinit";
+inline const char *sccsid_inl = "@(#) inline";
+
+void f() {}
+
+// ===========================================================================
+// Storage-duration filtering (STORAGE)
+// ===========================================================================
+
+// 10. A file-scope pointer with static storage duration is preserved.
+const char *keep = "@(#) keep";
+
+namespace S {
+// 11. A thread_local variable (N renamed to S to avoid redefinition) is
+// diagnosed and receives no metadata.
+thread_local const char *tl = "@(#) tl";
+} // namespace S
+
+// 12. The 'static' specifier changes linkage only; the storage duration is
+// still thread, so this is diagnosed as well.
+static thread_local const char *stl = "@(#) stl";
+
+// 13. A thread_local static data member (A renamed to T) is diagnosed and
+// receives no metadata.
+struct T {
+ static thread_local const char *tm;
+};
+thread_local const char *T::tm = "@(#) tm";
+
+// 14. Function-local static (f renamed to g) — name-matched, so diagnosed by
+// Sema (see the Sema tests); receives no metadata either way.
+void g() { static const char *fn = "@(#) fn"; (void)fn; }
+
+// ===========================================================================
+// Sources — list-parsing edge cases (SPACE, DUP)
+// ===========================================================================
+
+// 15. Simple arrays used only by the SPACE/DUP checks.
+char foo[] = "@(#) foo";
+char bar[] = "@(#) bar";
+
+// ===========================================================================
+// CHECK patterns — mangled-name matching
+// ===========================================================================
+
+// 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::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]+}}
+
+// A::x and B::ver are name-matched static data members: emitted as plain
+// external definitions with no !loadtime_comment (the {{$}} anchors prove
+// no metadata attachment).
+// CHECK-DAG: @_ZN1A1xE = global ptr @{{.*}}, align {{[0-9]+}}{{$}}
+// CHECK-DAG: @_ZN1B3verE = global ptr @{{.*}}, 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
+
+// Eligible C++ forms: static constexpr (internal, constant), constinit
+// (external), and inline (linkonce_odr) are all preserved.
+// CHECK-DAG: @_ZL9sccsid_ce = internal constant ptr @[[CE_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[CE_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constexpr\00", align {{[0-9]+}}
+// CHECK-DAG: @sccsid_ci = global ptr @[[CI_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[CI_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constinit\00", align {{[0-9]+}}
+// CHECK-DAG: @sccsid_inl = linkonce_odr global ptr @[[INL_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[INL_STR]] = private unnamed_addr constant [12 x i8] c"@(#) inline\00", align {{[0-9]+}}
+
+// The six supported matched globals are preserved in llvm.compiler.used;
+// the two static data members are not.
+// CHECK: @llvm.compiler.used = appending global [6 x ptr]
+// CHECK-SAME: @x
+// CHECK-SAME: @_ZN1N1xE
+// CHECK-SAME: @_ZN1NL3ptrE
+// CHECK-SAME: @_ZL9sccsid_ce
+// CHECK-SAME: @sccsid_ci
+// CHECK-SAME: @sccsid_inl
+// CHECK-SAME: section "llvm.metadata"
+
+// ===========================================================================
+// NOEMIT patterns — listed variables of non-plain-char element type
+// are silently ignored and, being unreferenced, not emitted at all.
+// ===========================================================================
+
+// NOEMIT-NOT: @_ZL4wstr
+// NOEMIT-NOT: @_ZL6u16str
+
+// ===========================================================================
+// STORAGE patterns — thread_local variables get no metadata
+// ===========================================================================
+
+// Only the file-scope static-duration variable is preserved: it has metadata
+// and appears in llvm.compiler.used. The thread_local variables are diagnosed
+// by Sema and receive neither.
+// STORAGE: @keep = {{.*}}!loadtime_comment
+// STORAGE-NOT: @_ZN1S2tlE = {{.*}}!loadtime_comment
+// STORAGE-NOT: @_ZL3stl = {{.*}}!loadtime_comment
+// STORAGE-NOT: @_ZN1T2tmE = {{.*}}!loadtime_comment
+// STORAGE-NOT: @_ZZ1gvE2fn = {{.*}}!loadtime_comment
+// STORAGE: @llvm.compiler.used = appending global [1 x ptr] [ptr @keep], section "llvm.metadata"
+
+// ===========================================================================
+// SPACE/DUP patterns — list-parsing edge cases
+// ===========================================================================
+
+// "foo, bar": leading space means ' bar' matches nothing; only foo is preserved.
+// 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"
+
+// "foo,foo": duplicate name preserves the variable exactly once.
+// 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"
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
new file mode 100644
index 0000000000000..24b8aedf46f3d
--- /dev/null
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -0,0 +1,40 @@
+// Verify that -mloadtime-comment-vars= diagnoses named C variables it cannot
+// preserve. The diagnostics are produced by Sema and fire even with
+// -fsyntax-only.
+//
+// Covered cases:
+// - volatile-qualified pointer (char *volatile)
+// - pointer-to-volatile character (volatile char *)
+// - volatile character array (volatile char[])
+// - thread-local variable (__thread, not static storage duration)
+// - pointer not initialized directly with a string literal
+// - valid const char array — no diagnostic
+
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn \
+// RUN: -fsyntax-only -verify %s
+
+// A volatile-qualified pointer is diagnosed.
+char *volatile vol_ptr = "@(#) vol ptr"; // expected-warning {{'vol_ptr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// A pointer to volatile characters is diagnosed.
+volatile char *vol_char = "@(#) vol char"; // expected-warning {{'vol_char' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// A volatile character array is diagnosed.
+volatile char vol_arr[] = "@(#) vol arr"; // expected-warning {{'vol_arr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
+
+// A thread-local variable does not have static storage duration and is
+// diagnosed.
+__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}}
+
+// A pointer bound to another object rather than a string literal is
+// diagnosed.
+static const char target[] = "@(#) target";
+const char *ind_ptr = target; // expected-warning {{'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; no diagnostic is expected.
+const char const_arr[] = "@(#) const arr";
+
+// A function-local static: name-matched, so diagnosed rather than silently
+// ignored.
+void h(void) { static char lfn[] = "@(#) lfn"; (void)lfn; } // expected-warning {{'lfn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
new file mode 100644
index 0000000000000..d49ff9257a102
--- /dev/null
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -0,0 +1,117 @@
+// Verify that -mloadtime-comment-vars= diagnoses C++ variables it cannot
+// preserve.
+//
+// Three scenarios are covered, each with its own set of named variables and
+// its own -verify prefix:
+//
+// storage — storage duration: thread_local variables and a name-matched
+// function-local static are diagnosed; a namespace-scope
+// variable is accepted without diagnostic.
+// init — initializer form: dynamically initialized pointers, and
+// constant initializers that are not a direct string literal
+// (pointer to another global, consteval call, user-defined
+// literal), are diagnosed. (-std=c++20 for consteval.)
+// kinds — unsupported declaration kinds: name-matched static data
+// members (out-of-line, in-class inline, instantiated from a
+// class template) and variable template specializations
+// (explicit and implicit) are diagnosed.
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn \
+// RUN: -fsyntax-only -verify=storage %s
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,p_ind,p_ce,p_udl \
+// RUN: -fsyntax-only -verify=init %s
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZN2B23sidE,_ZN2B34isidE,_Z2vtIcE,_Z2vtIiE,_ZN2SCIiE1mE \
+// RUN: -fsyntax-only -verify=kinds %s
+
+// ---- storage: storage-duration cases ----------------------------------------
+
+// A namespace-scope variable with static storage duration is supported; no
+// diagnostic is expected.
+const char *keep = "@(#) keep";
+
+namespace N {
+// A thread_local variable has thread storage duration rather than static,
+// so it is diagnosed.
+thread_local const char *tl = "@(#) tl"; // storage-warning {{'tl' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
+} // namespace N
+
+// 'static' here changes linkage only; storage duration is still thread.
+static thread_local const char *stl = "@(#) stl"; // storage-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;
+};
+// The static-data-member reason outranks the storage-duration one.
+thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+
+// Function-local static: a name match demonstrates intent, so it is
+// diagnosed rather than silently ignored.
+void f() { static const char *fn = "@(#) fn"; (void)fn; } // storage-warning {{'fn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
+
+// ---- init: initializer-form cases --------------------------------------------
+
+const char *make();
+
+// A pointer bound directly to a string literal is supported; no diagnostic
+// is expected.
+const char *p_ok = "@(#) p_ok";
+
+// An array initialized from a string literal is supported; no diagnostic is
+// expected.
+char arr_ok[] = "@(#) arr_ok";
+
+// A constant character array referenced by the pointer below.
+const char src[] = "@(#) src";
+
+// Dynamic initialization: value set by a start-up constructor; the string
+// would not be present in the object file at load time.
+const char *p_dyn = make(); // init-warning {{'p_dyn' named in '-mloadtime-comment-vars=' is not constant-initialized and will not be preserved}}
+
+// This pointer is constant-initialized but bound to another global rather
+// than a string literal, so it is diagnosed.
+const char *p_ind = src; // init-warning {{'p_ind' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+
+// Constant-initialized via an immediate (consteval) call. The initializer is
+// a call, not a direct string literal — the pointed-to string is not
+// guaranteed to be in this object file — so it is diagnosed, not preserved.
+consteval const char *make_ce() { return "@(#) ce"; }
+const char *p_ce = make_ce(); // init-warning {{'p_ce' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+
+// A user-defined literal is likewise a call underneath: constant-initialized
+// but not a direct string literal, so it is diagnosed.
+typedef __SIZE_TYPE__ size_t;
+constexpr const char *operator""_id(const char *str, size_t) { return str; }
+const char *p_udl = "@(#) udl"_id; // init-warning {{'p_udl' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+
+// ---- kinds: unsupported declaration kinds ------------------------------------
+
+// An out-of-line static data member definition is diagnosed.
+struct B2 {
+ static const char *sid;
+};
+const char *B2::sid = "@(#) b2"; // kinds-warning {{'sid' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+
+// An in-class inline static data member (C++17) is diagnosed as well.
+struct B3 {
+ static inline const char *isid = "@(#) b3"; // kinds-warning {{'isid' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+};
+
+// Variable template: the explicit specialization is diagnosed at its own
+// definition; the implicit specialization is diagnosed at the pattern, in
+// the TU that instantiates it, with a note at the point of instantiation.
+template <class T> const char *vt = "@(#) vt"; // kinds-warning {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+template <> const char *vt<char> = "@(#) vtc"; // kinds-warning {{'vt<char>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+const char *use_vt = vt<int>; // kinds-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
+
+// Static data member instantiated from a class template — diagnosed at the
+// member's pattern definition when the specialization is instantiated.
+template <class T> struct SC {
+ static const char *m;
+};
+template <class T> const char *SC<T>::m = "@(#) scm"; // kinds-warning {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+const char *use_scm = SC<int>::m; // kinds-note {{in instantiation of static data member 'SC<int>::m' requested here}}
diff --git a/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
index ff09388f9c71b..d63b065e1d3ce 100644
--- a/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
+++ b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
@@ -1,21 +1,21 @@
-; RUN: opt -passes=lower-comment-string -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-O0
+; RUN: opt -passes=lower-comment-string -S %s -o - | FileCheck %s
; Verify that lower-comment-string is enabled by default on all opt pipelines.
-; RUN: opt --O0 -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-O0
-; RUN: opt --O1 -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-ON
-; RUN: opt --O2 -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-ON
-; RUN: opt --O3 -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-ON
+; RUN: opt --O0 -S %s -o - | FileCheck %s
+; RUN: opt --O1 -S %s -o - | FileCheck %s
+; RUN: opt --O2 -S %s -o - | FileCheck %s
+; RUN: opt --O3 -S %s -o - | FileCheck %s
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
@.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 loadtime_comment_vars_gv = internal global ptr @.loadtime_comment_vars.str, align 4, !loadtime_comment !0
@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:
- ret void
+ ret void
}
define i32 @main() {
entry:
@@ -31,13 +31,13 @@ entry:
; 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]]
+; CHECK: define void @f0()
+; CHECK-SAME: !implicit.ref ![[MD:[0-9]+]]
+; CHECK-SAME: !implicit.ref ![[MD2:[0-9]+]]
+; CHECK: define {{.*}}i32 @main()
+; CHECK-SAME: !implicit.ref ![[MD]]
+; CHECK-SAME: !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}
+; CHECK: ![[MD]] = !{ptr @[[LOADTIME_COMMENT_STR]]}
+; CHECK: ![[MD2]] = !{ptr @loadtime_comment_vars_gv}
>From 38485ce5f6ce9530cfbc7c23c5d3fabaa1f7ef70 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 30 Jul 2026 20:40:04 +0530
Subject: [PATCH 10/31] [Clang][AIX] Preserve -mloadtime-comment-vars variables
across serialization
Named internal-linkage variables were silently dropped when their
definition crossed a PCH or C++20 named-module (BMI-to-object) boundary:
DeclMustBeEmitted did not know the implicit attribute, so the PCH writer
never marked the unreferenced variable as required, and for named modules
only non-discardable variables are recorded in the module-initializer
lists that EmitModuleInitializers walks. Teach DeclMustBeEmitted about
the attribute (the CodeGen-side check becomes redundant and is removed)
and record validated internal-linkage module-purview variables in the
module-initializer list.
Since DeclMustBeEmitted now sees the attribute, a preserved variable no
longer triggers -Wunused-const-variable: it is materially used.
Add tests for C++20 module preservation, importer behavior, and
instantiation diagnostics; PCH preservation; unused-warning suppression;
the non-AIX cc1 no-op; and char8_t element-type exclusion. Document the
modules/PCH rules.
---
clang/docs/LanguageExtensions.md | 13 ++-
clang/lib/AST/ASTContext.cpp | 6 ++
clang/lib/CodeGen/CodeGenModule.cpp | 3 +-
clang/lib/Sema/SemaDecl.cpp | 10 ++
.../PowerPC/loadtime-comment-vars-modules.cpp | 101 ++++++++++++++++++
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 6 +-
clang/test/PCH/loadtime-comment-vars.c | 28 +++++
clang/test/Sema/loadtime-comment-vars.c | 23 +++-
8 files changed, 180 insertions(+), 10 deletions(-)
create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
create mode 100644 clang/test/PCH/loadtime-comment-vars.c
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index a937a3beb1649..893724dc33233 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6575,9 +6575,8 @@ Syntax:
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, class-scoped, or internal-linkage
-variable (for example, a file-scope `static`) must be named using its mangled
-form:
+Itanium C++ ABI, so a namespace-scoped or internal-linkage variable (for
+example, a file-scope `static`) must be named using its mangled form:
```c++
namespace N { char sccsid[] = "@(#) MyApp Version 1.0"; } // N::sccsid -> _ZN1N6sccsidE
@@ -6626,8 +6625,12 @@ initializer are silently skipped, as are names that are not defined in the
translation unit.
For C++20 modules, a named variable defined in a module unit is processed when
-that module unit itself is compiled to object code, and the option applies to
-that compilation.
+the module unit itself is compiled, and the option must be present on that
+compilation -- in a two-phase build, the step that builds the module
+interface. Importing translation units do not re-emit the variable. A variable
+attached to a named module is matched by its module-attached mangled name (for
+example, `export char ver[];` in module `M` is `_ZW1M3ver`). The same applies
+to a precompiled header: the option must be present when the PCH is built.
Example:
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ad80074723817..e47a42a75ba3a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13324,6 +13324,12 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
if (D->hasAttr<AliasAttr>() || D->hasAttr<UsedAttr>())
return true;
+ // Variables validated for '-mloadtime-comment-vars=' must reach the object
+ // file even when unreferenced, including when the definition is loaded
+ // from a PCH or module file rather than parsed in this compilation.
+ if (D->hasAttr<LoadTimeCommentVarAttr>())
+ return true;
+
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
// Forward declarations aren't required.
if (!FD->doesThisDeclarationHaveABody())
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 23628ff0b9870..db1b02ea5bda2 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4426,8 +4426,7 @@ bool CodeGenModule::MustBeEmitted(const ValueDecl *Global) {
(VD->getStorageDuration() == SD_Static ||
VD->getStorageDuration() == SD_Thread)) ||
(CodeGenOpts.KeepStaticConsts && VD->getStorageDuration() == SD_Static &&
- VD->getType().isConstQualified()) ||
- VD->hasAttr<LoadTimeCommentVarAttr>()))
+ VD->getType().isConstQualified())))
return true;
return getContext().DeclMustBeEmitted(Global);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 0959ee29b2069..d02e4c39011f8 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15553,6 +15553,16 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
VD->addAttr(
LoadTimeCommentVarAttr::CreateImplicit(S.Context, VD->getLocation()));
+
+ // When a module unit is compiled to object code from its serialized form,
+ // CodeGen finds unreferenced module-purview variables through the
+ // module-initializer lists, and CheckCompleteVariableDeclaration records
+ // only non-discardable variables there (before this attribute is attached).
+ // Record an internal-linkage variable so its definition reaches CodeGen in
+ // that compilation.
+ if (Module *M = S.getCurrentModule())
+ if (isDiscardableGVALinkage(S.Context.GetGVALinkageForVariable(VD)))
+ S.Context.addModuleInitializer(M, VD);
}
void Sema::ProcessLoadTimeCommentVar(VarDecl *VD) {
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
new file mode 100644
index 0000000000000..93ee263a843fd
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
@@ -0,0 +1,101 @@
+// Test -mloadtime-comment-vars= across a C++20 named-module boundary. Five
+// scenarios, each named by its FileCheck or -verify prefix:
+//
+// MOD — the module unit is built to a BMI with the option and then
+// compiled to IR from the BMI (the two-phase flow build systems
+// use). Exported, module-linkage, and internal-linkage variables
+// are all preserved: the implicit attribute is serialized, and
+// the internal variable reaches CodeGen via the
+// module-initializer list.
+// NOOPT + — the same module unit built to a BMI without the option and
+// NOOPTNOT compiled to IR with it: nothing is preserved. The option
+// applies to the compilation of the module unit itself, where
+// semantic analysis runs.
+// IMPORT + — an importing TU naming the module-owned variable: the
+// IMPORTNOT variable is defined in the module unit, not here, so it is
+// neither re-emitted nor preserved here, and the module-internal
+// variable does not leak into the importer.
+// verify — specializations instantiated here from the imported template
+// definitions are diagnosed in this TU, at the pattern location
+// in the module interface, with a note at the instantiation
+// point.
+//
+// Source IR symbol Expected treatment
+// ------ --------- ------------------
+// ver _ZW1M3ver exported: preserved when the module unit is
+// compiled with the option
+// build _ZW1M5build module linkage: preserved likewise
+// priv _ZL4priv internal linkage: preserved likewise; never
+// emitted by an importing TU
+// vt<int> _ZW1M2vtIiE instantiated in the importer: diagnosed there
+// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: diagnosed there
+
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -emit-module-interface %t/m.cppm -o %t/m-noopt.pcm
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -emit-llvm %t/m-noopt.pcm -o %t/m-noopt.ll
+// RUN: FileCheck %s --check-prefix=NOOPT < %t/m-noopt.ll
+// RUN: FileCheck %s --check-prefix=NOOPTNOT < %t/m-noopt.ll
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -fmodule-file=M=%t/m.pcm -mloadtime-comment-vars=_ZW1M3ver \
+// RUN: -emit-llvm %t/use.cpp -o %t/use.ll
+// RUN: FileCheck %s --check-prefix=IMPORT < %t/use.ll
+// RUN: FileCheck %s --check-prefix=IMPORTNOT < %t/use.ll
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -fmodule-file=M=%t/m.pcm \
+// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
+// RUN: -fsyntax-only -verify %t/use.cpp
+
+// All three variables carry the metadata and are kept in llvm.compiler.used
+// when the module unit is compiled from its BMI with the option.
+// MOD-DAG: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1, !loadtime_comment ![[MD:[0-9]+]]
+// MOD-DAG: @_ZW1M5build = global [18 x i8] c"@(#) module build\00", align 1, !loadtime_comment ![[MD]]
+// MOD-DAG: @_ZL4priv = internal global [17 x i8] c"@(#) module priv\00", align 1, !loadtime_comment ![[MD]]
+// MOD-DAG: @llvm.compiler.used = appending global [3 x ptr]
+
+// Without the option at BMI-build time nothing is preserved: the exported
+// variable is an ordinary global (the {{$}} anchor proves no metadata), and
+// the unreferenced internal variable is not emitted at all.
+// NOOPT: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1{{$}}
+// NOOPTNOT-NOT: !loadtime_comment
+// NOOPTNOT-NOT: @llvm.compiler.used
+// NOOPTNOT-NOT: @_ZL4priv
+
+// The importer instantiates the templates (ordinary linkonce_odr definitions,
+// no metadata — the {{$}} anchor proves it) but emits neither the module-owned
+// variable it names nor the module-internal one.
+// IMPORT: @_ZW1M2vtIiE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
+// IMPORTNOT-NOT: @_ZW1M3ver
+// IMPORTNOT-NOT: @_ZW1M5build
+// IMPORTNOT-NOT: @_ZL4priv
+// IMPORTNOT-NOT: @llvm.compiler.used
+// IMPORTNOT-NOT: !loadtime_comment
+
+//--- m.cppm
+export module M;
+export char ver[] = "@(#) module ver";
+char build[] = "@(#) module build";
+static char priv[] = "@(#) module priv";
+export template <class T> const char *vt = "@(#) vt";
+export template <class T> struct S { static const char *m; };
+template <class T> const char *S<T>::m = "@(#) sdm";
+export inline void touch() {}
+
+//--- use.cpp
+import M;
+const char *u1 = vt<int>; // expected-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
+const char *u2 = S<int>::m; // expected-note {{in instantiation of static data member 'S<int>::m' requested here}}
+// expected-warning at m.cppm:5 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+// expected-warning at m.cppm:7 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 1f40b9e093bc1..c5ffadec678ee 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -28,6 +28,8 @@
// C::info _ZN1C4infoE no definition in this TU: skipped
// wstr _ZL4wstr wchar_t element type: ignored, not emitted
// u16str _ZL6u16str char16_t element type: ignored, not emitted
+// u8str _ZL5u8str char8_t element type: ignored, not emitted
+// not_string not_string int: unsupported type, silently skipped
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
@@ -44,7 +46,7 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,_ZL4wstr,_ZL6u16str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -107,6 +109,7 @@ int not_string = 7;
// emitted at all.
static wchar_t wstr[] = L"@(#) wide";
static char16_t u16str[] = u"@(#) u16";
+static char8_t u8str[] = u8"@(#) u8";
// 9. Eligible C++ declaration forms. Uses of these constant-fold, so without
// the option none of them would be emitted at all — the forced emission is
@@ -204,6 +207,7 @@ char bar[] = "@(#) bar";
// NOEMIT-NOT: @_ZL4wstr
// NOEMIT-NOT: @_ZL6u16str
+// NOEMIT-NOT: @_ZL5u8str
// ===========================================================================
// STORAGE patterns — thread_local variables get no metadata
diff --git a/clang/test/PCH/loadtime-comment-vars.c b/clang/test/PCH/loadtime-comment-vars.c
new file mode 100644
index 0000000000000..703824a7e2ee6
--- /dev/null
+++ b/clang/test/PCH/loadtime-comment-vars.c
@@ -0,0 +1,28 @@
+// Test that a variable named in -mloadtime-comment-vars= and defined in a
+// precompiled header is still preserved when the PCH is used: Sema attaches
+// the implicit attribute when the header is compiled, the attribute is
+// serialized in the PCH, and the unreferenced definition must still reach
+// CodeGen in the including compilation. The first invocation compiles the
+// header textually as a baseline; the PCH pair must produce the same result.
+
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid \
+// RUN: -include %s -emit-llvm -o - %s | FileCheck %s
+
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid \
+// RUN: -emit-pch -o %t %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid \
+// RUN: -include-pch %t -emit-llvm -o - %s | FileCheck %s
+
+#ifndef HEADER
+#define HEADER
+
+static char sccsid[] = "@(#) pch sccsid";
+
+#else
+
+void use(void) {}
+
+#endif
+
+// CHECK: @sccsid = internal global [16 x i8] c"@(#) pch sccsid\00", align 1, !loadtime_comment !0
+// CHECK: @llvm.compiler.used = appending global [1 x ptr] [ptr @sccsid], section "llvm.metadata"
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index 24b8aedf46f3d..2b4df338c009d 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -9,11 +9,22 @@
// - thread-local variable (__thread, not static storage duration)
// - pointer not initialized directly with a string literal
// - valid const char array — no diagnostic
+// - a preserved variable counts as used: no -Wunused-const-variable for it,
+// while an unlisted static const variable still warns
+//
+// The quiet scenario compiles the same source for a non-AIX target: the
+// option is a silent no-op at the cc1 layer there (the driver, tested
+// separately, warns and drops it), so no diagnostics at all are expected.
-// RUN: %clang_cc1 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn \
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
// RUN: -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
+// RUN: -fsyntax-only -verify=quiet %s
+// quiet-no-diagnostics
+
// A volatile-qualified pointer is diagnosed.
char *volatile vol_ptr = "@(#) vol ptr"; // expected-warning {{'vol_ptr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
@@ -38,3 +49,11 @@ const char const_arr[] = "@(#) const arr";
// A function-local static: name-matched, so diagnosed rather than silently
// ignored.
void h(void) { static char lfn[] = "@(#) lfn"; (void)lfn; } // expected-warning {{'lfn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
+
+// A preserved variable is materially used — it is forced into the object
+// file — so -Wunused-const-variable does not fire for it.
+static const char kept[] = "@(#) kept";
+
+// An unlisted static const variable is unaffected by the option and still
+// gets the unused warning.
+static const char dropped[] = "@(#) dropped"; // expected-warning {{unused variable 'dropped'}}
>From 8a3e4e9e73c93d5588622958272fc52ea01d13b6 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 6 Aug 2026 14:11:52 +0530
Subject: [PATCH 11/31] nit: Minor updates to the comments
---
clang/lib/Sema/SemaDecl.cpp | 5 ++---
clang/test/Sema/loadtime-comment-vars.c | 3 +--
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d02e4c39011f8..612f4da2fee3b 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15557,9 +15557,8 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// When a module unit is compiled to object code from its serialized form,
// CodeGen finds unreferenced module-purview variables through the
// module-initializer lists, and CheckCompleteVariableDeclaration records
- // only non-discardable variables there (before this attribute is attached).
- // Record an internal-linkage variable so its definition reaches CodeGen in
- // that compilation.
+ // only non-discardable variables there. Record an internal-linkage variable
+ // so its definition reaches CodeGen in that compilation.
if (Module *M = S.getCurrentModule())
if (isDiscardableGVALinkage(S.Context.GetGVALinkageForVariable(VD)))
S.Context.addModuleInitializer(M, VD);
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index 2b4df338c009d..769058dba3491 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -13,8 +13,7 @@
// while an unlisted static const variable still warns
//
// The quiet scenario compiles the same source for a non-AIX target: the
-// option is a silent no-op at the cc1 layer there (the driver, tested
-// separately, warns and drops it), so no diagnostics at all are expected.
+// option is a silent no-op at the cc1 layer there, so no diagnostics at all are expected.
// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
>From 3bef7e719e65382135c21ea7dc2f772687e30220 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 6 Aug 2026 15:20:44 +0530
Subject: [PATCH 12/31] [Clang][AIX] Error on -mloadtime-comment-vars in cc1
for non-AIX targets
The driver continues to warn and drop the option for unsupported targets;
cc1, reachable directly or via -Xclang, now rejects it with
err_drv_unsupported_opt_for_target, mirroring the -mabi=quadword-atomics
and -mxcoff-roptr checks.
---
clang/docs/LanguageExtensions.md | 1 -
clang/lib/Frontend/CompilerInvocation.cpp | 6 +++++
clang/lib/Sema/SemaDecl.cpp | 5 ----
.../CodeGen/PowerPC/loadtime-comment-vars.c | 23 ++++---------------
clang/test/Sema/loadtime-comment-vars.c | 12 +++++-----
5 files changed, 16 insertions(+), 31 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 893724dc33233..c25bb1b8f0697 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6667,7 +6667,6 @@ the named source variables in place using `.ref` directives. Both sets of
strings appear in the final object file independently.
(langext-evaluating-object-size)=
-
## Evaluating Object Size
Clang supports the builtins `__builtin_object_size` and
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 037647f76bd1d..7b053875c640b 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2192,6 +2192,12 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
<< A->getSpelling() << T.str();
}
+ if (Arg *A = Args.getLastArg(OPT_mloadtime_comment_vars_EQ)) {
+ if (!T.isOSAIX())
+ Diags.Report(diag::err_drv_unsupported_opt_for_target)
+ << A->getSpelling() << T.str();
+ }
+
bool NeedLocTracking = false;
if (!Opts.OptRecordFile.empty())
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 612f4da2fee3b..7e6eca3418152 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15466,11 +15466,6 @@ void Sema::CheckThreadLocalForLargeAlignment(VarDecl *VD) {
/// string variables so CodeGen preserves them as loadtime identifying
/// strings, and warn when a named variable cannot be preserved.
static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
- // The driver restricts the option to AIX, but cc1 can be invoked directly
- // with any triple, so the target is checked here rather than inherited from
- // driver gating.
- if (!S.Context.getTargetInfo().getTriple().isOSAIX())
- return;
// Declarations that cannot be name-matched are silently skipped: an
// automatic variable has no symbol of its own, and neither does a template
// pattern (only its specializations do, and those are processed
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index e76f209e01636..5f6ae414cd650 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -5,9 +5,12 @@
// kept alive in llvm.compiler.used, even when otherwise unreferenced;
// * unsupported or unlisted variables are silently ignored and, when
// unreferenced, are not emitted at all;
-// * on non-AIX targets the option has no effect;
// * names are matched against the mangled IR name: in C that is the source
// identifier; in C++ a file-scope static mangles (sccsid -> _ZL6sccsid).
+//
+// Non-AIX behavior is covered elsewhere: the driver warns and drops the
+// option (clang/test/Driver/mloadtime-comment-vars.c) and cc1 rejects it
+// with an error (clang/test/Sema/loadtime-comment-vars.c).
// C, 32-bit and 64-bit AIX.
@@ -25,12 +28,6 @@
// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar < %t-cxx.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=_ZL9copyright -DBUILDNUM=_ZL12build_number -DBUILDDATA=_ZL10build_data -DUSTR=_ZL4ustr -DSSTR=_ZL4sstr < %t-cxx.ll
-// Non-AIX target: the option has no effect.
-// RUN: %clang_cc1 -O2 -triple x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version -emit-llvm -disable-llvm-passes -o %t-linux.ll %s
-// RUN: FileCheck %s --check-prefix=LINUX < %t-linux.ll
-// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-linux.ll
-// RUN: FileCheck %s --check-prefix=NOPRESERVE < %t-linux.ll
-
// 1. A string pointer named in the list is preserved.
static char *sccsid = "@(#) sccsid Version 1.0";
@@ -99,15 +96,3 @@ void foo() {}
// NOEMIT-NOT: @[[USTR]]
// NOEMIT-NOT: @[[SSTR]]
// NOEMIT-NOT: @not_defined_here
-
-// On non-AIX targets a referenced variable is still emitted normally, just
-// not preserved.
-// LINUX: @active = internal global ptr
-// LINUX: define {{.*}}void @bar
-
-// On non-AIX targets nothing is preserved: no metadata, and the unreferenced
-// statics -- listed or not -- are not emitted.
-// NOPRESERVE-NOT: loadtime_comment
-// NOPRESERVE-NOT: @sccsid
-// NOPRESERVE-NOT: @version
-// NOPRESERVE-NOT: @same_copyright
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index 769058dba3491..b3bc0a6ff703b 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -12,17 +12,17 @@
// - a preserved variable counts as used: no -Wunused-const-variable for it,
// while an unlisted static const variable still warns
//
-// The quiet scenario compiles the same source for a non-AIX target: the
-// option is a silent no-op at the cc1 layer there, so no diagnostics at all are expected.
+// The NONAIX scenario invokes cc1 directly for a non-AIX target: the option
+// is rejected with an error.
// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
// RUN: -fsyntax-only -verify %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
-// RUN: -fsyntax-only -verify=quiet %s
-// quiet-no-diagnostics
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu \
+// RUN: -mloadtime-comment-vars=sccsid \
+// RUN: -fsyntax-only %s 2>&1 | FileCheck --check-prefix=NONAIX %s
+// NONAIX: error: unsupported option '-mloadtime-comment-vars=' for target 'x86_64-unknown-linux-gnu'
// A volatile-qualified pointer is diagnosed.
char *volatile vol_ptr = "@(#) vol ptr"; // expected-warning {{'vol_ptr' named in '-mloadtime-comment-vars=' is volatile-qualified and will not be preserved}}
>From c69579b61d0721f02700fe41e9620133eac81b42 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 3 Sep 2026 10:06:58 +0530
Subject: [PATCH 13/31] nit: Minor updates to LanguageExtensions.md
---
clang/docs/LanguageExtensions.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index c25bb1b8f0697..b3f6cf61ed79b 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6555,7 +6555,7 @@ 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
+## Preserving Identifying Variables with -mloadtime-comment-vars
The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
mangled variable names that should be preserved in the final object file as
@@ -6658,7 +6658,7 @@ source.o:
Built 2026-05-24
```
-Interaction with `#pragma comment(copyright, ...)`:
+### 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
>From c52c2aba021ed9d66959586ce8d772e548a7d7da Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 25 Sep 2026 02:19:44 +0530
Subject: [PATCH 14/31] [Clang][AIX] Address review: enum_select diagnostic,
cached mangler, structured-binding test
---
.../clang/Basic/DiagnosticSemaKinds.td | 16 +++++---
clang/include/clang/Sema/Sema.h | 5 +++
clang/lib/Sema/Sema.cpp | 1 +
clang/lib/Sema/SemaDecl.cpp | 41 ++++++++-----------
.../lib/Sema/SemaTemplateInstantiateDecl.cpp | 3 +-
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 28 +++++++++----
6 files changed, 54 insertions(+), 40 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 392e3d211e5f6..2ee5a1a8f8d57 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3863,12 +3863,16 @@ def warn_not_xl_compatible
def note_misaligned_member_used_here : Note<
"passing byval argument %0 with potentially incompatible alignment here">;
def warn_loadtime_comment_var_not_preserved
- : Warning<"%0 named in '-mloadtime-comment-vars=' %select{is "
- "volatile-qualified|does not have static storage duration|is "
- "not constant-initialized|is not initialized with a string "
- "literal|is a function-local variable|is a static data member|"
- "is a variable template specialization}1 and will not be "
- "preserved">,
+ : Warning<"%0 named in '-mloadtime-comment-vars=' "
+ "%enum_select<LoadTimeCommentVarReason>{"
+ "%Volatile{is volatile-qualified}|"
+ "%BadStorage{does not have static storage duration}|"
+ "%DynamicInit{is not constant-initialized}|"
+ "%NotStringLiteral{is not initialized with a string literal}|"
+ "%FunctionLocal{is a function-local variable}|"
+ "%StaticDataMember{is a static data member}|"
+ "%TemplateSpecialization{is a variable template specialization}"
+ "}1 and will not be preserved">,
InGroup<LoadtimeCommentVar>;
def warn_redeclaration_without_attribute_prev_attribute_ignored : Warning<
"%q0 redeclared without %1 attribute: previous %1 ignored">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index aa6622fc4c172..7ce90d58c55e3 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -120,6 +120,7 @@ class ASTConsumer;
class ASTContext;
class ASTDeclReader;
class ASTMutationListener;
+class ASTNameGenerator;
class ASTReader;
class ASTWriter;
class CXXBasePath;
@@ -4198,6 +4199,10 @@ class Sema final : public SemaBase {
/// Exposed for instantiated variable definitions, which do not pass
/// through FinalizeDeclaration.
void ProcessLoadTimeCommentVar(VarDecl *VD);
+ /// Lazily created mangler for '-mloadtime-comment-vars=' name matching. Only
+ /// allocated if the option is in use.
+ std::unique_ptr<ASTNameGenerator> LoadTimeCommentVarNameGenerator;
+
DeclGroupPtrTy FinalizeDeclaratorGroup(Scope *S, const DeclSpec &DS,
ArrayRef<Decl *> Group);
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 21f71d7f8b40e..796c24e50e7b7 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -21,6 +21,7 @@
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Mangle.h"
#include "clang/AST/PrettyDeclStackTrace.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/TypeOrdering.h"
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7e6eca3418152..8b64eb477c1f9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15466,6 +15466,9 @@ void Sema::CheckThreadLocalForLargeAlignment(VarDecl *VD) {
/// string variables so CodeGen preserves them as loadtime identifying
/// strings, and warn when a named variable cannot be preserved.
static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
+ if (S.getLangOpts().LoadTimeCommentVars.empty() || VD->isInvalidDecl())
+ return;
+
// Declarations that cannot be name-matched are silently skipped: an
// automatic variable has no symbol of its own, and neither does a template
// pattern (only its specializations do, and those are processed
@@ -15495,54 +15498,47 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// Names are matched against the mangled name, as it appears in the object
// file. For plain C file-scope variables this is the source identifier; for
// C++ variables it is the mangled symbol.
+ if (!S.LoadTimeCommentVarNameGenerator)
+ S.LoadTimeCommentVarNameGenerator =
+ std::make_unique<ASTNameGenerator>(S.Context);
if (!S.getLangOpts().isLoadTimeCommentVar(
- ASTNameGenerator(S.Context).getName(VD)))
+ S.LoadTimeCommentVarNameGenerator->getName(VD)))
return;
- // Indices of the %select in warn_loadtime_comment_var_not_preserved.
- enum {
- Volatile,
- BadStorage,
- DynamicInit,
- NotStringLiteral,
- FunctionLocal,
- StaticDataMember,
- TemplateSpecialization
- };
- int Reason = -1;
+ std::optional<unsigned> Reason;
if (VD->isLocalVarDecl())
// Only file- and namespace-scope variables are supported. A name match
// on anything else demonstrates intent (scope participates in the
// mangled name), so the unsupported kinds are diagnosed rather than
// silently ignored.
- Reason = FunctionLocal;
+ Reason = diag::LoadTimeCommentVarReason::FunctionLocal;
else if (isa<VarTemplateSpecializationDecl>(VD))
- Reason = TemplateSpecialization;
+ Reason = diag::LoadTimeCommentVarReason::TemplateSpecialization;
else if (VD->isStaticDataMember())
- Reason = StaticDataMember;
+ Reason = diag::LoadTimeCommentVarReason::StaticDataMember;
else if (VD->getStorageDuration() != SD_Static)
// The string must have static storage duration; a thread-local variable
// is not preserved.
- Reason = BadStorage;
+ Reason = diag::LoadTimeCommentVarReason::BadStorage;
else if (Ty.isVolatileQualified() || Pointee.isVolatileQualified())
// The intended usage does not intersect with use cases where the character
// array or the pointer to it is volatile-qualified; such variables are not
// preserved.
- Reason = Volatile;
+ Reason = diag::LoadTimeCommentVarReason::Volatile;
else if (!VD->hasConstantInitialization())
// 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.
- Reason = DynamicInit;
+ Reason = diag::LoadTimeCommentVarReason::DynamicInit;
else if (PT && !isa<StringLiteral>(VD->getInit()->IgnoreParenImpCasts()))
// 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.
- Reason = NotStringLiteral;
+ Reason = diag::LoadTimeCommentVarReason::NotStringLiteral;
- if (Reason >= 0) {
+ if (Reason) {
S.Diag(VD->getLocation(), diag::warn_loadtime_comment_var_not_preserved)
- << VD << Reason;
+ << VD << *Reason;
return;
}
@@ -15682,8 +15678,7 @@ void Sema::FinalizeDeclaration(Decl *ThisDecl) {
// Validate variables named in '-mloadtime-comment-vars=': supported string
// variables get an implicit attribute that CodeGen uses to preserve them;
// named variables that cannot be preserved are diagnosed.
- if (!getLangOpts().LoadTimeCommentVars.empty() && !VD->isInvalidDecl())
- processForLoadTimeCommentVar(*this, VD);
+ ProcessLoadTimeCommentVar(VD);
const DeclContext *DC = VD->getDeclContext();
// If there's a #pragma GCC visibility in scope, and this isn't a class
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index ec06aeb87ea79..ad8c98db5a85f 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6848,8 +6848,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation,
AnalysisWarnings.issueWarningsForRegisteredVarDecl(Var);
// Variables named in '-mloadtime-comment-vars=' are normally processed in
// FinalizeDeclaration, which instantiated definitions do not reach.
- if (!getLangOpts().LoadTimeCommentVars.empty() && !Var->isInvalidDecl())
- ProcessLoadTimeCommentVar(Var);
+ ProcessLoadTimeCommentVar(Var);
}
// This variable may have local implicit instantiations that need to be
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index c5ffadec678ee..182430ce1d991 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -33,6 +33,8 @@
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
+// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
+// DecompositionDecl owns the storage)
//
// Names used in the storage scenario (namespaces and structs are renamed to
// avoid redefinition against the matching-scenario symbols):
@@ -46,7 +48,7 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -118,33 +120,39 @@ static constexpr const char *sccsid_ce = "@(#) constexpr";
constinit const char *sccsid_ci = "@(#) constinit";
inline const char *sccsid_inl = "@(#) inline";
+// 10. Structured binding. The bindings a/b/c are BindingDecls with no storage
+// of their own; the hidden DecompositionDecl (a VarDecl) owns the array,
+// and the symbol is the mangled name of that VarDecl, so it is the
+// DecompositionDecl that is preserved.
+auto [a, b, c] = "ab";
+
void f() {}
// ===========================================================================
// Storage-duration filtering (STORAGE)
// ===========================================================================
-// 10. A file-scope pointer with static storage duration is preserved.
+// 11. A file-scope pointer with static storage duration is preserved.
const char *keep = "@(#) keep";
namespace S {
-// 11. A thread_local variable (N renamed to S to avoid redefinition) is
+// 12. A thread_local variable (N renamed to S to avoid redefinition) is
// diagnosed and receives no metadata.
thread_local const char *tl = "@(#) tl";
} // namespace S
-// 12. The 'static' specifier changes linkage only; the storage duration is
+// 13. The 'static' specifier changes linkage only; the storage duration is
// still thread, so this is diagnosed as well.
static thread_local const char *stl = "@(#) stl";
-// 13. A thread_local static data member (A renamed to T) is diagnosed and
+// 14. A thread_local static data member (A renamed to T) is diagnosed and
// receives no metadata.
struct T {
static thread_local const char *tm;
};
thread_local const char *T::tm = "@(#) tm";
-// 14. Function-local static (f renamed to g) — name-matched, so diagnosed by
+// 15. Function-local static (f renamed to g) — name-matched, so diagnosed by
// Sema (see the Sema tests); receives no metadata either way.
void g() { static const char *fn = "@(#) fn"; (void)fn; }
@@ -152,7 +160,7 @@ void g() { static const char *fn = "@(#) fn"; (void)fn; }
// Sources — list-parsing edge cases (SPACE, DUP)
// ===========================================================================
-// 15. Simple arrays used only by the SPACE/DUP checks.
+// 16. Simple arrays used only by the SPACE/DUP checks.
char foo[] = "@(#) foo";
char bar[] = "@(#) bar";
@@ -188,16 +196,18 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @[[CI_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constinit\00", align {{[0-9]+}}
// CHECK-DAG: @sccsid_inl = linkonce_odr global ptr @[[INL_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[INL_STR]] = private unnamed_addr constant [12 x i8] c"@(#) inline\00", align {{[0-9]+}}
+// CHECK-DAG: @_ZDC1a1b1cE = internal constant [3 x i8] c"ab\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// The six supported matched globals are preserved in llvm.compiler.used;
+// The seven supported matched globals are preserved in llvm.compiler.used;
// the two static data members are not.
-// CHECK: @llvm.compiler.used = appending global [6 x ptr]
+// CHECK: @llvm.compiler.used = appending global [7 x ptr]
// CHECK-SAME: @x
// CHECK-SAME: @_ZN1N1xE
// CHECK-SAME: @_ZN1NL3ptrE
// CHECK-SAME: @_ZL9sccsid_ce
// CHECK-SAME: @sccsid_ci
// CHECK-SAME: @sccsid_inl
+// CHECK-SAME: @_ZDC1a1b1cE
// CHECK-SAME: section "llvm.metadata"
// ===========================================================================
>From d44958fbefb5da627d261715d233727397298f51 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 25 Sep 2026 02:22:00 +0530
Subject: [PATCH 15/31] [Clang][AIX] Diagnose name-matched
-mloadtime-comment-vars variables of unsupported type
---
clang/docs/LanguageExtensions.md | 9 ++++---
.../clang/Basic/DiagnosticSemaKinds.td | 3 ++-
clang/lib/Sema/SemaDecl.cpp | 23 ++++++++++------
.../CodeGen/PowerPC/loadtime-comment-vars.c | 21 ++++++++-------
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 26 ++++++++++---------
clang/test/Sema/loadtime-comment-vars.c | 8 +++++-
clang/test/Sema/loadtime-comment-vars.cpp | 9 +++++--
7 files changed, 62 insertions(+), 37 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index b3f6cf61ed79b..7e0773cb5eef3 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6619,10 +6619,11 @@ dynamically initialized, or is a pointer not bound to a string literal, is
diagnosed with a warning and is not preserved. The same applies to name-matched
variables of unsupported kinds: function-local `static` variables, static data
members, and variable template specializations (implicit specializations are
-diagnosed in each translation unit that instantiates them). Variables of an
-unsupported type -- for example, an `int` or a `struct` -- or without an
-initializer are silently skipped, as are names that are not defined in the
-translation unit.
+diagnosed in each translation unit that instantiates them). A name-matched
+variable of any other type -- for example, an `int` or a `struct` -- is
+likewise diagnosed. A definition without an initializer is silently skipped.
+Names that match no variable defined in the translation unit are also
+silently ignored.
For C++20 modules, a named variable defined in a module unit is processed when
the module unit itself is compiled, and the option must be present on that
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 2ee5a1a8f8d57..57bdc9bd4e138 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3871,7 +3871,8 @@ def warn_loadtime_comment_var_not_preserved
"%NotStringLiteral{is not initialized with a string literal}|"
"%FunctionLocal{is a function-local variable}|"
"%StaticDataMember{is a static data member}|"
- "%TemplateSpecialization{is a variable template specialization}"
+ "%TemplateSpecialization{is a variable template specialization}|"
+ "%UnsupportedType{does not have a plain char pointer or array type}"
"}1 and will not be preserved">,
InGroup<LoadtimeCommentVar>;
def warn_redeclaration_without_attribute_prev_attribute_ignored : Warning<
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8b64eb477c1f9..72e4c284a8185 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15480,20 +15480,20 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
if (VD->isThisDeclarationADefinition(S.Context) != VarDecl::Definition)
return;
- // Only plain `char` pointers/arrays with an initializer are supported; a
- // matched variable of any other form (int, struct, wide or explicitly
- // signed/unsigned character types, no initializer, ...) is silently
- // ignored.
+ // A definition without an initializer carries no string and is silently
+ // skipped.
+ if (!VD->hasInit())
+ return;
+
+ // Extract the character type a pointer points to or an array holds; it is
+ // null for any other type, which is classified (and diagnosed) below once
+ // the name has matched.
QualType Ty = VD->getType();
const PointerType *PT = Ty->getAsCanonical<PointerType>();
const ArrayType *AT = PT ? nullptr : S.Context.getAsArrayType(Ty);
QualType Pointee = PT ? PT->getPointeeType()
: AT ? AT->getElementType()
: QualType();
- if (Pointee.isNull() ||
- !S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy) ||
- !VD->hasInit())
- return;
// Names are matched against the mangled name, as it appears in the object
// file. For plain C file-scope variables this is the source identifier; for
@@ -15516,6 +15516,13 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
Reason = diag::LoadTimeCommentVarReason::TemplateSpecialization;
else if (VD->isStaticDataMember())
Reason = diag::LoadTimeCommentVarReason::StaticDataMember;
+ else if (Pointee.isNull() ||
+ !S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy))
+ // Only plain `char` pointers/arrays are supported. A name match on a
+ // variable of any other type (int, struct, wide or explicitly
+ // signed/unsigned character types, ...) still demonstrates intent, so it
+ // is diagnosed.
+ Reason = diag::LoadTimeCommentVarReason::UnsupportedType;
else if (VD->getStorageDuration() != SD_Static)
// The string must have static storage duration; a thread-local variable
// is not preserved.
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index 5f6ae414cd650..06862bf390db4 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -3,8 +3,10 @@
// * supported forms (plain-char pointer/array with a string-literal
// initializer) named in the list get !loadtime_comment metadata and are
// kept alive in llvm.compiler.used, even when otherwise unreferenced;
-// * unsupported or unlisted variables are silently ignored and, when
-// unreferenced, are not emitted at all;
+// * unlisted variables, and listed variables of an unsupported type, are
+// not preserved and, when unreferenced, are not emitted at all (the
+// unsupported-type diagnostic itself is covered by the Sema tests; it is
+// silenced here with -Wno-loadtime-comment-var);
// * names are matched against the mangled IR name: in C that is the source
// identifier; in C++ a file-scope static mangles (sccsid -> _ZL6sccsid).
//
@@ -14,8 +16,8 @@
// C, 32-bit and 64-bit AIX.
-// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c32.ll %s
-// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c64.ll %s
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c32.ll %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c64.ll %s
// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c32.ll
// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c64.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-c32.ll
@@ -24,7 +26,7 @@
// The same source as C++: internal-linkage statics are matched by mangled
// name. (-w silences the C++ writable-strings compatibility warning for the
// legacy `static char *` idiom.)
-// RUN: %clang_cc1 -x c++ -w -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=_ZL6sccsid,_ZL7version,_ZL12build_number,_ZL14same_copyright,_ZL6active,not_defined_here,_ZL8tdefchar,_ZL4ustr,_ZL4sstr -emit-llvm -disable-llvm-passes -o %t-cxx.ll %s
+// RUN: %clang_cc1 -x c++ -w -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=_ZL6sccsid,_ZL7version,_ZL12build_number,_ZL14same_copyright,_ZL6active,not_defined_here,_ZL8tdefchar,_ZL4ustr,_ZL4sstr -emit-llvm -disable-llvm-passes -o %t-cxx.ll %s
// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar < %t-cxx.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=_ZL9copyright -DBUILDNUM=_ZL12build_number -DBUILDDATA=_ZL10build_data -DUSTR=_ZL4ustr -DSSTR=_ZL4sstr < %t-cxx.ll
@@ -37,7 +39,7 @@ static char version[] = "@(#) Copyright Version 2.0";
// 3. Const string (not in the list; unreferenced, so not emitted)
static const char *copyright = "@(#) Copyright 2026";
-// 4. Integer (in the list but unsupported type; not emitted)
+// 4. Integer (in the list but unsupported type: diagnosed, not emitted)
static int build_number = 12345;
// 5. Struct (not in the list and unsupported type; not emitted)
@@ -62,7 +64,7 @@ typedef char CHAR;
static CHAR *tdefchar = "@(#) typedef char";
// 10. These are listed, but their element type is not plain char, so they
-// are silently ignored and not emitted.
+// are diagnosed (see the Sema tests), not preserved, and not emitted.
static unsigned char ustr[] = "@(#) unsigned char string";
static signed char sstr[] = "@(#) signed char string";
@@ -88,8 +90,9 @@ void foo() {}
// CHECK-SAME: section "llvm.metadata"
// The unlisted const string, the unsupported-type variables (including the
-// listed signed/unsigned char strings, whose element type is not plain char),
-// and the extern declaration are not emitted in any configuration.
+// listed signed/unsigned char strings, whose element type is not plain char
+// and which are therefore diagnosed rather than preserved), and the extern
+// declaration are not emitted in any configuration.
// NOEMIT-NOT: @[[COPYRIGHT]]
// NOEMIT-NOT: @[[BUILDNUM]]
// NOEMIT-NOT: @[[BUILDDATA]]
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 182430ce1d991..c999ed1f32f70 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -7,7 +7,9 @@
// diagnosed and left unpreserved (still emitted as ordinary
// definitions). A second pass (NOEMIT) over the same output
// proves that listed variables of non-plain-char element type
-// (wchar_t, char16_t) are silently ignored and not emitted.
+// (wchar_t, char16_t, char8_t) and of non-character type are
+// not preserved and not emitted. Their diagnostic is covered
+// by the Sema tests and silenced here.
//
// STORAGE — storage-duration filtering: thread_local variables are
// diagnosed by Sema and receive no metadata; a function-local
@@ -26,10 +28,10 @@
// A::x _ZN1A1xE static data member: diagnosed, unpreserved
// B::ver _ZN1B3verE static data member: diagnosed, unpreserved
// C::info _ZN1C4infoE no definition in this TU: skipped
-// wstr _ZL4wstr wchar_t element type: ignored, not emitted
-// u16str _ZL6u16str char16_t element type: ignored, not emitted
-// u8str _ZL5u8str char8_t element type: ignored, not emitted
-// not_string not_string int: unsupported type, silently skipped
+// wstr _ZL4wstr wchar_t element type: diagnosed, not emitted
+// u16str _ZL6u16str char16_t element type: diagnosed, not emitted
+// u8str _ZL5u8str char8_t element type: diagnosed, not emitted
+// not_string not_string int: unsupported type: diagnosed, not emitted
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
@@ -47,7 +49,7 @@
// T::tm _ZN1T2tmE thread_local static data member: diagnosed
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
-// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
+// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
@@ -102,13 +104,13 @@ const char *B::ver = "@(#) class ver";
// so it is silently skipped.
struct C { static const char *info; };
-// 7. An int has an unsupported type and must not be tagged, even though its
-// IR name matches a listed name.
+// 7. An int has an unsupported type: it is diagnosed (see the Sema tests) and
+// must not be tagged, even though its IR name matches a listed name.
int not_string = 7;
// 8. These are listed, but their element type is not plain char, so they are
-// silently ignored and, being unreferenced internal-linkage statics, not
-// emitted at all.
+// diagnosed (see the Sema tests), not preserved, and -- being unreferenced
+// internal-linkage statics -- not emitted at all.
static wchar_t wstr[] = L"@(#) wide";
static char16_t u16str[] = u"@(#) u16";
static char8_t u8str[] = u8"@(#) u8";
@@ -211,8 +213,8 @@ char bar[] = "@(#) bar";
// CHECK-SAME: section "llvm.metadata"
// ===========================================================================
-// NOEMIT patterns — listed variables of non-plain-char element type
-// are silently ignored and, being unreferenced, not emitted at all.
+// NOEMIT patterns — listed variables of non-plain-char element type are
+// diagnosed, not preserved, and, being unreferenced, not emitted at all.
// ===========================================================================
// NOEMIT-NOT: @_ZL4wstr
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index b3bc0a6ff703b..3a4b1811f8000 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -8,6 +8,7 @@
// - volatile character array (volatile char[])
// - thread-local variable (__thread, not static storage duration)
// - pointer not initialized directly with a string literal
+// - name-matched variables of an unsupported type (int, unsigned char)
// - valid const char array — no diagnostic
// - a preserved variable counts as used: no -Wunused-const-variable for it,
// while an unlisted static const variable still warns
@@ -16,7 +17,7 @@
// is rejected with an error.
// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept,notchar,ustr \
// RUN: -fsyntax-only -verify %s
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu \
@@ -56,3 +57,8 @@ static const char kept[] = "@(#) kept";
// An unlisted static const variable is unaffected by the option and still
// gets the unused warning.
static const char dropped[] = "@(#) dropped"; // expected-warning {{unused variable 'dropped'}}
+
+// A name match on a variable of any other type still demonstrates intent, so
+// it is diagnosed rather than silently ignored.
+static int notchar = 42; // expected-warning {{'notchar' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
+static unsigned char ustr[] = "@(#) u"; // expected-warning {{'ustr' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
index d49ff9257a102..d72c823c3a9f7 100644
--- a/clang/test/Sema/loadtime-comment-vars.cpp
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -6,7 +6,9 @@
//
// storage — storage duration: thread_local variables and a name-matched
// function-local static are diagnosed; a namespace-scope
-// variable is accepted without diagnostic.
+// variable is accepted without diagnostic. Also covers a
+// name-matched variable of an unsupported (wide character)
+// type, which is diagnosed.
// init — initializer form: dynamically initialized pointers, and
// constant initializers that are not a direct string literal
// (pointer to another global, consteval call, user-defined
@@ -17,7 +19,7 @@
// (explicit and implicit) are diagnosed.
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn \
+// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn,_ZL4wstr \
// RUN: -fsyntax-only -verify=storage %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -53,6 +55,9 @@ thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '
// diagnosed rather than silently ignored.
void f() { static const char *fn = "@(#) fn"; (void)fn; } // storage-warning {{'fn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
+// A name match on a variable of an unsupported type is diagnosed as well.
+static wchar_t wstr[] = L"@(#) w"; // storage-warning {{'wstr' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
+
// ---- init: initializer-form cases --------------------------------------------
const char *make();
>From 8e11ba9eaf29bde29de4e2146668605e030af2d2 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 25 Sep 2026 14:25:04 +0530
Subject: [PATCH 16/31] [Clang][AIX] Document and test -mloadtime-comment-vars
with inline variables across modules
---
clang/docs/LanguageExtensions.md | 33 +++++++-----
.../PowerPC/loadtime-comment-vars-modules.cpp | 54 ++++++++++++-------
2 files changed, 54 insertions(+), 33 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 7e0773cb5eef3..4e3cfb073de11 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6592,9 +6592,10 @@ Valid variable types:
A variable named in the list must meet all of these conditions to be
preserved:
-- It must be defined at file or namespace scope. A name-matched function-local
- `static` variable, static data member, or variable template specialization
- is not supported and is diagnosed.
+- It must be defined at file or namespace scope; `inline`, `constexpr`, and
+ `constinit` variables are supported. A name-matched function-local `static`
+ variable, static data member, or variable template specialization is not
+ supported and is diagnosed.
- Its type must be a character pointer (`char *`, `const char *`) or a
character array (`char[]`, `const char[]`). The character type must be plain
`char`: variables of `signed char`, `unsigned char`, and the wide and
@@ -6618,20 +6619,24 @@ 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. The same applies to name-matched
variables of unsupported kinds: function-local `static` variables, static data
-members, and variable template specializations (implicit specializations are
-diagnosed in each translation unit that instantiates them). A name-matched
-variable of any other type -- for example, an `int` or a `struct` -- is
-likewise diagnosed. A definition without an initializer is silently skipped.
-Names that match no variable defined in the translation unit are also
-silently ignored.
+members, and variable template specializations (an explicit specialization is
+diagnosed at its definition, an implicit one in each translation unit that
+instantiates it). A name-matched variable of any other type -- for example, an
+`int` or a `struct` -- is likewise diagnosed. A definition without an
+initializer is silently skipped. Names that match no variable defined in the
+translation unit are also silently ignored, since the option is typically
+given to every compilation of a build.
For C++20 modules, a named variable defined in a module unit is processed when
the module unit itself is compiled, and the option must be present on that
-compilation -- in a two-phase build, the step that builds the module
-interface. Importing translation units do not re-emit the variable. A variable
-attached to a named module is matched by its module-attached mangled name (for
-example, `export char ver[];` in module `M` is `_ZW1M3ver`). The same applies
-to a precompiled header: the option must be present when the PCH is built.
+compilation -- in a two-phase build, the step that produces the module
+interface file. Giving the option to an importing translation unit has no
+effect on variables owned by the module. An `inline` variable is preserved in
+the module unit and in every importing translation unit that re-emits it. A
+variable attached to a named module is matched by its module-attached mangled
+name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
+same applies to a precompiled header: the option must be present when the PCH
+is built.
Example:
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
index 93ee263a843fd..0fb2d9975e11b 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
@@ -3,10 +3,12 @@
//
// MOD — the module unit is built to a BMI with the option and then
// compiled to IR from the BMI (the two-phase flow build systems
-// use). Exported, module-linkage, and internal-linkage variables
-// are all preserved: the implicit attribute is serialized, and
-// the internal variable reaches CodeGen via the
-// module-initializer list.
+// use). Exported, module-linkage, internal-linkage, and inline
+// variables are all preserved: the implicit attribute is
+// serialized, and the internal and inline variables reach
+// CodeGen via the module-initializer list. The same IR is
+// produced whether or not the option is repeated on the
+// codegen step: the BMI already records the result.
// NOOPT + — the same module unit built to a BMI without the option and
// NOOPTNOT compiled to IR with it: nothing is preserved. The option
// applies to the compilation of the module unit itself, where
@@ -14,7 +16,10 @@
// IMPORT + — an importing TU naming the module-owned variable: the
// IMPORTNOT variable is defined in the module unit, not here, so it is
// neither re-emitted nor preserved here, and the module-internal
-// variable does not leak into the importer.
+// variable does not leak into the importer. The inline variable
+// it references is re-emitted here as usual for inline
+// variables, and that copy is preserved as well because the
+// attribute travels with the declaration in the BMI.
// verify — specializations instantiated here from the imported template
// definitions are diagnosed in this TU, at the pattern location
// in the module interface, with a note at the instantiation
@@ -27,22 +32,27 @@
// build _ZW1M5build module linkage: preserved likewise
// priv _ZL4priv internal linkage: preserved likewise; never
// emitted by an importing TU
+// iv _ZW1M2iv exported inline: preserved by the module unit
+// (emitted there although unreferenced) and by
+// every importer that references it
// vt<int> _ZW1M2vtIiE instantiated in the importer: diagnosed there
// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: diagnosed there
// RUN: split-file %s %t
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv,_ZW1M2iv \
// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv,_ZW1M2iv \
+// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -emit-module-interface %t/m.cppm -o %t/m-noopt.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv,_ZW1M2iv \
// RUN: -emit-llvm %t/m-noopt.pcm -o %t/m-noopt.ll
// RUN: FileCheck %s --check-prefix=NOOPT < %t/m-noopt.ll
// RUN: FileCheck %s --check-prefix=NOOPTNOT < %t/m-noopt.ll
@@ -58,36 +68,41 @@
// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
// RUN: -fsyntax-only -verify %t/use.cpp
-// All three variables carry the metadata and are kept in llvm.compiler.used
-// when the module unit is compiled from its BMI with the option.
+// All four variables carry the metadata and are kept in llvm.compiler.used
+// when the module unit is compiled from a BMI that was built with the option.
// MOD-DAG: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1, !loadtime_comment ![[MD:[0-9]+]]
// MOD-DAG: @_ZW1M5build = global [18 x i8] c"@(#) module build\00", align 1, !loadtime_comment ![[MD]]
// MOD-DAG: @_ZL4priv = internal global [17 x i8] c"@(#) module priv\00", align 1, !loadtime_comment ![[MD]]
-// MOD-DAG: @llvm.compiler.used = appending global [3 x ptr]
+// MOD-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8, !loadtime_comment ![[MD]]
+// MOD-DAG: @llvm.compiler.used = appending global [4 x ptr]
// Without the option at BMI-build time nothing is preserved: the exported
// variable is an ordinary global (the {{$}} anchor proves no metadata), and
-// the unreferenced internal variable is not emitted at all.
+// the unreferenced internal and inline variables are not emitted at all.
// NOOPT: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1{{$}}
// NOOPTNOT-NOT: !loadtime_comment
// NOOPTNOT-NOT: @llvm.compiler.used
// NOOPTNOT-NOT: @_ZL4priv
+// NOOPTNOT-NOT: @_ZW1M2iv
// The importer instantiates the templates (ordinary linkonce_odr definitions,
-// no metadata — the {{$}} anchor proves it) but emits neither the module-owned
-// variable it names nor the module-internal one.
-// IMPORT: @_ZW1M2vtIiE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
+// no metadata — the {{$}} anchor proves it) and re-emits the inline variable
+// it references, with the metadata carried over from the BMI; that copy is
+// the only entry in its llvm.compiler.used. It emits neither the module-owned
+// non-inline variable it names nor the module-internal one.
+// IMPORT-DAG: @_ZW1M2vtIiE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
+// IMPORT-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8, !loadtime_comment !{{[0-9]+}}
+// IMPORT: @llvm.compiler.used = appending global [1 x ptr] [ptr @_ZW1M2iv]
// IMPORTNOT-NOT: @_ZW1M3ver
// IMPORTNOT-NOT: @_ZW1M5build
// IMPORTNOT-NOT: @_ZL4priv
-// IMPORTNOT-NOT: @llvm.compiler.used
-// IMPORTNOT-NOT: !loadtime_comment
//--- m.cppm
export module M;
export char ver[] = "@(#) module ver";
char build[] = "@(#) module build";
static char priv[] = "@(#) module priv";
+export inline const char *iv = "@(#) module inline";
export template <class T> const char *vt = "@(#) vt";
export template <class T> struct S { static const char *m; };
template <class T> const char *S<T>::m = "@(#) sdm";
@@ -97,5 +112,6 @@ export inline void touch() {}
import M;
const char *u1 = vt<int>; // expected-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
const char *u2 = S<int>::m; // expected-note {{in instantiation of static data member 'S<int>::m' requested here}}
-// expected-warning at m.cppm:5 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
-// expected-warning at m.cppm:7 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+const char *u3 = iv;
+// expected-warning at m.cppm:6 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+// expected-warning at m.cppm:8 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
>From c3aa66e5c5dbdb6aed02d3781070c5512b54b297 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 11:32:55 +0530
Subject: [PATCH 17/31] [Clang][AIX] Forward every -mloadtime-comment-vars
occurrence to cc1; document retention and repeated use
---
clang/docs/LanguageExtensions.md | 24 +++++++++++--------
clang/lib/Driver/ToolChains/Clang.cpp | 6 ++---
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 20 +++++++++++++++-
clang/test/Driver/mloadtime-comment-vars.c | 16 ++++++++++++-
4 files changed, 51 insertions(+), 15 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 4e3cfb073de11..116b137543f6f 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6573,6 +6573,8 @@ Syntax:
-mloadtime-comment-vars=<var1>[,<var2>,...]
```
+The option can be repeated; the names from all occurrences are combined.
+
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 internal-linkage variable (for
@@ -6627,16 +6629,18 @@ initializer is silently skipped. Names that match no variable defined in the
translation unit are also silently ignored, since the option is typically
given to every compilation of a build.
-For C++20 modules, a named variable defined in a module unit is processed when
-the module unit itself is compiled, and the option must be present on that
-compilation -- in a two-phase build, the step that produces the module
-interface file. Giving the option to an importing translation unit has no
-effect on variables owned by the module. An `inline` variable is preserved in
-the module unit and in every importing translation unit that re-emits it. A
-variable attached to a named module is matched by its module-attached mangled
-name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
-same applies to a precompiled header: the option must be present when the PCH
-is built.
+For C++20 modules, a named variable defined in a module unit is processed
+using the option as specified when the module unit itself is compiled -- in a
+two-phase build, when the module interface file is produced. Giving the option
+to an importing translation unit has no effect on variables owned by the
+module. Retention of a variable to load time depends on the linker retaining
+function definitions from the translation unit that was compiled to preserve
+it, since those functions carry the references that keep the string alive. An
+`inline` variable is preserved in the object files for the module unit and for
+every importing translation unit that re-emits it. A variable attached to a
+named module is matched by its module-attached mangled name (for example,
+`export char ver[];` in module `M` is `_ZW1M3ver`). The same applies to a
+precompiled header: the option must be present when the PCH is built.
Example:
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 18a39713d20d6..e4c3e4c045d25 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6402,13 +6402,13 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("-funwind-tables=1");
// Forward the loadtime-comment vars option to cc1 only on targets that
- // support it.
+ // support it. Every occurrence is forwarded; cc1 combines the lists.
if (Arg *A = Args.getLastArg(options::OPT_mloadtime_comment_vars_EQ)) {
if (Triple.isOSAIX())
- A->render(Args, CmdArgs);
+ Args.AddAllArgs(CmdArgs, options::OPT_mloadtime_comment_vars_EQ);
else
D.Diag(diag::warn_drv_unsupported_option_for_target)
- << A->getAsString(Args) << TripleStr;
+ << A->getSpelling() << TripleStr;
}
// Sframe unwind tables are independent of the other types. Although also
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index c999ed1f32f70..1ebe2766c5ef7 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -1,4 +1,4 @@
-// Test -mloadtime-comment-vars= IR output for C++ on AIX. Three scenarios
+// Test -mloadtime-comment-vars= IR output for C++ on AIX. Four scenarios
// are covered, each with its own set of named variables and check prefix:
//
// CHECK — mangled-name matching: file- and namespace-scope variables
@@ -17,6 +17,9 @@
//
// SPACE/DUP — list-parsing edge cases: a name with a leading space matches
// nothing; a duplicate name preserves the variable exactly once.
+// MULTI — multiple occurrences of the option on the command line combine
+// the lists; cc1 receives every occurrence in order and combines
+// them internally.
//
// Names used in the matching scenario:
//
@@ -67,6 +70,10 @@
// RUN: -mloadtime-comment-vars=foo,foo \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=DUP
+// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=foo -mloadtime-comment-vars=bar \
+// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=MULTI
+
// ===========================================================================
// Mangled-name matching
// ===========================================================================
@@ -248,3 +255,14 @@ char bar[] = "@(#) bar";
// 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"
+
+// ===========================================================================
+// MULTI option patterns — the option gien more than once
+// ===========================================================================
+
+// The option given twice: the lists are combined, so both are preserved.
+// MULTI-DAG: @foo = global [9 x i8] c"@(#) foo\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// MULTI-DAG: @bar = global [9 x i8] c"@(#) bar\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// MULTI: @llvm.compiler.used = appending global [2 x ptr]
+// MULTI-SAME: @foo
+// MULTI-SAME: @bar
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
index 68f01c39804e4..d1260c1bbc906 100644
--- a/clang/test/Driver/mloadtime-comment-vars.c
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -1,14 +1,28 @@
// 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
+// RUN: %clang -### -target powerpc-ibm-aix -mloadtime-comment-vars=sccsid -mloadtime-comment-vars=version,build %s 2>&1 | FileCheck %s --check-prefix=MULTI
+// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid -mloadtime-comment-vars=version %s 2>&1 | FileCheck %s --check-prefix=NONAIX-MULTI
+
// Verify the option is forwarded verbatim to cc1 for a supported target.
// CHECK: "-cc1"
// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
// Verify a warning is emitted and the option is NOT forwarded for an
// unsupported target.
-// NONAIX: warning: ignoring '-mloadtime-comment-vars=sccsid,version' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
+// NONAIX: warning: ignoring '-mloadtime-comment-vars=' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
// NONAIX: "-cc1"
// NONAIX-NOT: "-mloadtime-comment-vars=sccsid,version"
+// Verify every occurrence is forwarded, in order; cc1 combines the lists.
+// MULTI: "-cc1"
+// MULTI-SAME: "-mloadtime-comment-vars=sccsid"
+// MULTI-SAME: "-mloadtime-comment-vars=version,build"
+
+// Verify a repeated option on an unsupported target warns once and forwards
+// nothing.
+// NONAIX-MULTI: warning: ignoring '-mloadtime-comment-vars=' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
+// NONAIX-MULTI: "-cc1"
+// NONAIX-MULTI-NOT: "-mloadtime-comment-vars=
+
int main(void) { return 0; }
>From 4817d584f131f29430c4f5b68018ca327a2a3db2 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 11:55:51 +0530
Subject: [PATCH 18/31] Fix a typo in
clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
---
clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 1ebe2766c5ef7..2be1aeb30bf52 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -257,7 +257,7 @@ char bar[] = "@(#) bar";
// DUP-DAG: @llvm.compiler.used = appending global [1 x ptr] [ptr @foo], section "llvm.metadata"
// ===========================================================================
-// MULTI option patterns — the option gien more than once
+// MULTI option patterns — the option given more than once
// ===========================================================================
// The option given twice: the lists are combined, so both are preserved.
>From fcd36e8536b8a5ed424d7f1e5574cc1714f1ef19 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 12:26:21 +0530
Subject: [PATCH 19/31] [Clang][AIX] Document C-linkage and unnamed-namespace
names and the warning group. Test both forms
---
clang/docs/LanguageExtensions.md | 16 +++--
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 68 ++++++++++++-------
2 files changed, 54 insertions(+), 30 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 116b137543f6f..6f1aa96208c95 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6589,6 +6589,13 @@ static char build[] = "@(#) Level 42"; // build -> _ZL5
-mloadtime-comment-vars=_ZN1N6sccsidE,_ZL5build
```
+A variable declared with C language linkage (`extern "C"`) is not mangled and
+is named by its identifier. A variable in an unnamed namespace is mangled with
+the unnamed-namespace marker, for example `namespace { char anon[]; }` is
+`_ZN12_GLOBAL__N_14anonE`. Names are matched exactly against the mangled name;
+surrounding whitespace is not trimmed, so `-mloadtime-comment-vars=foo, bar`
+names `foo` and ` bar`.
+
Valid variable types:
A variable named in the list must meet all of these conditions to be
@@ -6624,10 +6631,11 @@ variables of unsupported kinds: function-local `static` variables, static data
members, and variable template specializations (an explicit specialization is
diagnosed at its definition, an implicit one in each translation unit that
instantiates it). A name-matched variable of any other type -- for example, an
-`int` or a `struct` -- is likewise diagnosed. A definition without an
-initializer is silently skipped. Names that match no variable defined in the
-translation unit are also silently ignored, since the option is typically
-given to every compilation of a build.
+`int` or a `struct` -- is likewise diagnosed. These diagnostics are in the
+`-Wloadtime-comment-var` group. A definition without an initializer is
+silently skipped. Names that match no variable defined in the translation unit
+are also silently ignored, since the option is typically given to every
+compilation of a build.
For C++20 modules, a named variable defined in a module unit is processed
using the option as specified when the module unit itself is compiled -- in a
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 2be1aeb30bf52..91a93601e72be 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -23,23 +23,25 @@
//
// Names used in the matching scenario:
//
-// Source IR symbol Expected treatment
-// ------ --------- ------------------
-// x x preserved (no mangling at C++ file scope)
-// N::x _ZN1N1xE preserved
-// N::ptr _ZN1NL3ptrE preserved ('static' gives internal linkage)
-// A::x _ZN1A1xE static data member: diagnosed, unpreserved
-// B::ver _ZN1B3verE static data member: diagnosed, unpreserved
-// C::info _ZN1C4infoE no definition in this TU: skipped
-// wstr _ZL4wstr wchar_t element type: diagnosed, not emitted
-// u16str _ZL6u16str char16_t element type: diagnosed, not emitted
-// u8str _ZL5u8str char8_t element type: diagnosed, not emitted
-// not_string not_string int: unsupported type: diagnosed, not emitted
-// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
-// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
-// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
-// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
-// DecompositionDecl owns the storage)
+// Source IR symbol Expected treatment
+// ------ --------- ------------------
+// x x preserved (no mangling at C++ file scope)
+// N::x _ZN1N1xE preserved
+// N::ptr _ZN1NL3ptrE preserved ('static' gives internal linkage)
+// A::x _ZN1A1xE static data member: diagnosed, unpreserved
+// B::ver _ZN1B3verE static data member: diagnosed, unpreserved
+// C::info _ZN1C4infoE no definition in this TU: skipped
+// wstr _ZL4wstr wchar_t element type: diagnosed, not emitted
+// u16str _ZL6u16str char16_t element type: diagnosed, not emitted
+// u8str _ZL5u8str char8_t element type: diagnosed, not emitted
+// not_string not_string int: unsupported type: diagnosed, not emitted
+// cver cver preserved (C linkage, not mangled)
+// anon _ZN12_GLOBAL__N_14anonE preserved (unnamed namespace)
+// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
+// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
+// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
+// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
+// DecompositionDecl owns the storage)
//
// Names used in the storage scenario (namespaces and structs are renamed to
// avoid redefinition against the matching-scenario symbols):
@@ -53,7 +55,7 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -137,31 +139,40 @@ auto [a, b, c] = "ab";
void f() {}
+// 11. C language linkage: the name is not mangled, so the identifier is the
+// IR name and the listed name.
+extern "C" char cver[] = "@(#) c linkage";
+
+// 12. An unnamed namespace mangles with the _GLOBAL__N_1 marker.
+namespace {
+char anon[] = "@(#) anon";
+} // namespace
+
// ===========================================================================
// Storage-duration filtering (STORAGE)
// ===========================================================================
-// 11. A file-scope pointer with static storage duration is preserved.
+// 13. A file-scope pointer with static storage duration is preserved.
const char *keep = "@(#) keep";
namespace S {
-// 12. A thread_local variable (N renamed to S to avoid redefinition) is
+// 14. A thread_local variable (N renamed to S to avoid redefinition) is
// diagnosed and receives no metadata.
thread_local const char *tl = "@(#) tl";
} // namespace S
-// 13. The 'static' specifier changes linkage only; the storage duration is
+// 15. The 'static' specifier changes linkage only; the storage duration is
// still thread, so this is diagnosed as well.
static thread_local const char *stl = "@(#) stl";
-// 14. A thread_local static data member (A renamed to T) is diagnosed and
+// 16. A thread_local static data member (A renamed to T) is diagnosed and
// receives no metadata.
struct T {
static thread_local const char *tm;
};
thread_local const char *T::tm = "@(#) tm";
-// 15. Function-local static (f renamed to g) — name-matched, so diagnosed by
+// 17. Function-local static (f renamed to g) — name-matched, so diagnosed by
// Sema (see the Sema tests); receives no metadata either way.
void g() { static const char *fn = "@(#) fn"; (void)fn; }
@@ -169,7 +180,7 @@ void g() { static const char *fn = "@(#) fn"; (void)fn; }
// Sources — list-parsing edge cases (SPACE, DUP)
// ===========================================================================
-// 16. Simple arrays used only by the SPACE/DUP checks.
+// 18. Simple arrays used only by the SPACE/DUP checks.
char foo[] = "@(#) foo";
char bar[] = "@(#) bar";
@@ -197,6 +208,9 @@ char bar[] = "@(#) bar";
// C::info has no definition — must not appear.
// CHECK-NOT: @_ZN1C4infoE
+// CHECK-DAG: @cver = global [15 x i8] c"@(#) c linkage\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @_ZN12_GLOBAL__N_14anonE = internal global [10 x i8] c"@(#) anon\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+
// Eligible C++ forms: static constexpr (internal, constant), constinit
// (external), and inline (linkonce_odr) are all preserved.
// CHECK-DAG: @_ZL9sccsid_ce = internal constant ptr @[[CE_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
@@ -207,9 +221,9 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @[[INL_STR]] = private unnamed_addr constant [12 x i8] c"@(#) inline\00", align {{[0-9]+}}
// CHECK-DAG: @_ZDC1a1b1cE = internal constant [3 x i8] c"ab\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// The seven supported matched globals are preserved in llvm.compiler.used;
+// The nine supported matched globals are preserved in llvm.compiler.used;
// the two static data members are not.
-// CHECK: @llvm.compiler.used = appending global [7 x ptr]
+// CHECK: @llvm.compiler.used = appending global [9 x ptr]
// CHECK-SAME: @x
// CHECK-SAME: @_ZN1N1xE
// CHECK-SAME: @_ZN1NL3ptrE
@@ -217,6 +231,8 @@ char bar[] = "@(#) bar";
// CHECK-SAME: @sccsid_ci
// CHECK-SAME: @sccsid_inl
// CHECK-SAME: @_ZDC1a1b1cE
+// CHECK-SAME: @cver
+// CHECK-SAME: @_ZN12_GLOBAL__N_14anonE
// CHECK-SAME: section "llvm.metadata"
// ===========================================================================
>From 2b9bd2c1d59df91c6bdc34b7868be9db29a6e995 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 14:31:43 +0530
Subject: [PATCH 20/31] [Clang][AIX] Skip mangling for -mloadtime-comment-vars
when no listed name contains the identifier
---
clang/docs/LanguageExtensions.md | 3 ++-
clang/lib/Sema/SemaDecl.cpp | 17 +++++++++++++
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 25 ++++++++++++-------
3 files changed, 35 insertions(+), 10 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 6f1aa96208c95..875b8b486a9da 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6594,7 +6594,8 @@ is named by its identifier. A variable in an unnamed namespace is mangled with
the unnamed-namespace marker, for example `namespace { char anon[]; }` is
`_ZN12_GLOBAL__N_14anonE`. Names are matched exactly against the mangled name;
surrounding whitespace is not trimmed, so `-mloadtime-comment-vars=foo, bar`
-names `foo` and ` bar`.
+names `foo` and ` bar`. A variable given an assembler label with `asm("label")`
+is named by that label.
Valid variable types:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 72e4c284a8185..7acc826419ad5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14,6 +14,7 @@
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
+#include "clang/AST/Attrs.inc"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
@@ -63,6 +64,7 @@
#include "clang/Sema/SemaWasm.h"
#include "clang/Sema/Template.h"
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/STLForwardCompat.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallPtrSet.h"
@@ -15495,6 +15497,21 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
: AT ? AT->getElementType()
: QualType();
+ // Mangling is comparatively expensive, so first check cheaply whether the
+ // source identifier appears in any listed name at all: both the Itanium
+ // mangling and an unmangled C name embed the identifier verbatim. A
+ // declaration without an identifier (a structured binding) has no such
+ // shortcut and is mangled directly; nor does a variable with an assembler
+ // label, since the label replaces the identifier in the object-file name.
+ if (const IdentifierInfo *II = VD->getIdentifier()) {
+ if (!VD->hasAttr<AsmLabelAttr>()) {
+ StringRef Name = II->getName();
+ if (llvm::none_of(
+ S.getLangOpts().LoadTimeCommentVars,
+ [Name](StringRef Listed) { return Listed.contains(Name); }))
+ return;
+ }
+ }
// Names are matched against the mangled name, as it appears in the object
// file. For plain C file-scope variables this is the source identifier; for
// C++ variables it is the mangled symbol.
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 91a93601e72be..602a75d90b809 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -37,6 +37,7 @@
// not_string not_string int: unsupported type: diagnosed, not emitted
// cver cver preserved (C linkage, not mangled)
// anon _ZN12_GLOBAL__N_14anonE preserved (unnamed namespace)
+// version asmid preserved (asm label)
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
@@ -55,7 +56,7 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -148,31 +149,35 @@ namespace {
char anon[] = "@(#) anon";
} // namespace
+// 13. An asm label replaces the mangled name in the object file, so the
+// variable is listed by its label.
+char version[] asm("asmid") = "@(#) asm label";
+
// ===========================================================================
// Storage-duration filtering (STORAGE)
// ===========================================================================
-// 13. A file-scope pointer with static storage duration is preserved.
+// 14. A file-scope pointer with static storage duration is preserved.
const char *keep = "@(#) keep";
namespace S {
-// 14. A thread_local variable (N renamed to S to avoid redefinition) is
+// 15. A thread_local variable (N renamed to S to avoid redefinition) is
// diagnosed and receives no metadata.
thread_local const char *tl = "@(#) tl";
} // namespace S
-// 15. The 'static' specifier changes linkage only; the storage duration is
+// 16. The 'static' specifier changes linkage only; the storage duration is
// still thread, so this is diagnosed as well.
static thread_local const char *stl = "@(#) stl";
-// 16. A thread_local static data member (A renamed to T) is diagnosed and
+// 17. A thread_local static data member (A renamed to T) is diagnosed and
// receives no metadata.
struct T {
static thread_local const char *tm;
};
thread_local const char *T::tm = "@(#) tm";
-// 17. Function-local static (f renamed to g) — name-matched, so diagnosed by
+// 18. Function-local static (f renamed to g) — name-matched, so diagnosed by
// Sema (see the Sema tests); receives no metadata either way.
void g() { static const char *fn = "@(#) fn"; (void)fn; }
@@ -180,7 +185,7 @@ void g() { static const char *fn = "@(#) fn"; (void)fn; }
// Sources — list-parsing edge cases (SPACE, DUP)
// ===========================================================================
-// 18. Simple arrays used only by the SPACE/DUP checks.
+// 19. Simple arrays used only by the SPACE/DUP checks.
char foo[] = "@(#) foo";
char bar[] = "@(#) bar";
@@ -210,6 +215,7 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @cver = global [15 x i8] c"@(#) c linkage\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @_ZN12_GLOBAL__N_14anonE = internal global [10 x i8] c"@(#) anon\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @asmid = global [15 x i8] c"@(#) asm label\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
// Eligible C++ forms: static constexpr (internal, constant), constinit
// (external), and inline (linkonce_odr) are all preserved.
@@ -221,9 +227,9 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @[[INL_STR]] = private unnamed_addr constant [12 x i8] c"@(#) inline\00", align {{[0-9]+}}
// CHECK-DAG: @_ZDC1a1b1cE = internal constant [3 x i8] c"ab\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// The nine supported matched globals are preserved in llvm.compiler.used;
+// The ten supported matched globals are preserved in llvm.compiler.used;
// the two static data members are not.
-// CHECK: @llvm.compiler.used = appending global [9 x ptr]
+// CHECK: @llvm.compiler.used = appending global [10 x ptr]
// CHECK-SAME: @x
// CHECK-SAME: @_ZN1N1xE
// CHECK-SAME: @_ZN1NL3ptrE
@@ -233,6 +239,7 @@ char bar[] = "@(#) bar";
// CHECK-SAME: @_ZDC1a1b1cE
// CHECK-SAME: @cver
// CHECK-SAME: @_ZN12_GLOBAL__N_14anonE
+// CHECK-SAME: @asmid
// CHECK-SAME: section "llvm.metadata"
// ===========================================================================
>From 769d5e5c467e0e0ccb39909ebd7e340ad800802f Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 15:01:28 +0530
Subject: [PATCH 21/31] [LLVM][NFC] Describe both producers of
!loadtime_comment in LowerCommentStringPass
---
.../Transforms/Utils/LowerCommentStringPass.h | 8 ++-
.../Utils/LowerCommentStringPass.cpp | 56 ++++++++++---------
2 files changed, 37 insertions(+), 27 deletions(-)
diff --git a/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h b/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
index f7402c738ca56..5772e75ee0250 100644
--- a/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
+++ b/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
@@ -1,10 +1,10 @@
-//===-- LowerCommentStringPass.h - Lower Comment string metadata --===//
+//===- LowerCommentStringPass.h - Lower loadtime comments -------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
-//===---------------------------------------------------------------------===//
+//===----------------------------------------------------------------------===//
#ifndef LLVM_TRANSFORMS_UTILS_LOWERCOMMENTSTRINGPASS_H
#define LLVM_TRANSFORMS_UTILS_LOWERCOMMENTSTRINGPASS_H
@@ -12,6 +12,10 @@
#include "llvm/IR/PassManager.h"
namespace llvm {
+/// Attach !implicit.ref metadata from every defined function to each global
+/// carrying !loadtime_comment metadata, so that the backend emits a reference
+/// that keeps the loadtime identifying string through linking. See the
+/// implementation file for the producers of such globals.
class LowerCommentStringPass
: public RequiredPassInfoMixin<LowerCommentStringPass> {
public:
diff --git a/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp b/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
index e788e484518d5..0822cf97eb3fd 100644
--- a/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
+++ b/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
@@ -1,41 +1,46 @@
-//===-- LowerCommentStringPass.cpp - Lower Comment string metadata -------===//
+//===- LowerCommentStringPass.cpp - Lower loadtime comment strings --------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
-//===---------------------------------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
-// This pass processes copyright comment strings created by Clang for
-// #pragma comment(copyright, ...) implementation.
+// This pass keeps loadtime identifying strings alive through linking.
//
-// Clang CodeGen creates weak_odr hidden constant string globals marked with
-// !loadtime_comment metadata and adds them to llvm.compiler.used. These globals
-// are placed in the __loadtime_comment section for better memory layout.
+// A loadtime identifying string is a global variable carrying
+// !loadtime_comment metadata. Clang produces such globals from two sources:
//
-// This pass attaches !implicit.ref metadata from every defined function to
-// each copyright string global. The PowerPC AIX backend recognizes this
-// metadata and emits a .ref directive, creating a relocation that prevents
-// the linker from discarding the string as long as the function is kept.
+// * #pragma comment(copyright, "..."): CodeGen creates a weak_odr hidden
+// unnamed_addr constant named __loadtime_comment_str_<hash> in the
+// __loadtime_comment section.
//
-// This pass is currently enabled for AIX targets only.
+// * -mloadtime-comment-vars=<names>: Sema attaches an implicit attribute to
+// each listed string variable, and CodeGen tags the variable's ordinary
+// definition. Its name, linkage, and section are unchanged.
//
-// Input IR (created by Clang):
-// @__loadtime_comment_str_HASH = weak_odr hidden unnamed_addr constant
-// [N x i8] c"Copyright\00", section "__loadtime_comment", align 1,
-// !loadtime_comment !0
-// @llvm.compiler.used = appending global [1 x ptr]
-// [ptr @__loadtime_comment_str_HASH], section "llvm.metadata"
+// Both producers also add the global to llvm.compiler.used, which keeps it
+// through IR optimization but not through linking.
+//
+// This pass attaches !implicit.ref metadata naming every such global to each
+// function defined in the module. The PowerPC backend for XCOFF lowers the
+// metadata to a .ref directive, which creates a relocation from the function's
+// csect to the string's csect. The linker then retains the string for as long
+// as it retains any function from the module.
+//
+// The pass runs only for XCOFF targets; elsewhere it is a no-op.
//
-// Output IR:
+// Input IR (the pragma producer is shown; a -mloadtime-comment-vars global
+// differs only in name, linkage, and section):
// @__loadtime_comment_str_HASH = weak_odr hidden unnamed_addr constant
// [N x i8] c"Copyright\00", section "__loadtime_comment", align 1,
// !loadtime_comment !0
// @llvm.compiler.used = appending global [1 x ptr]
// [ptr @__loadtime_comment_str_HASH], section "llvm.metadata"
//
-// define i32 @func() !implicit.ref !1 { ... }
-// !1 = !{ptr @__loadtime_comment_str_HASH}
+// Output IR: the globals are unchanged and every defined function gains
+// define i32 @func() !implicit.ref !1 { ... }
+// !1 = !{ptr @__loadtime_comment_str_HASH}
//
//===----------------------------------------------------------------------===//
@@ -63,18 +68,19 @@
using namespace llvm;
static cl::opt<bool>
- DisableCopyrightMetadata("disable-lower-comment-string", cl::ReallyHidden,
- cl::desc("Disable LowerCommentString pass."),
- cl::init(false));
+ DisableLowerCommentString("disable-lower-comment-string", cl::ReallyHidden,
+ cl::desc("Disable LowerCommentString pass."),
+ cl::init(false));
static bool isSupportedTarget(const Module &M) {
+ // The pass runs only for XCOFF targets; elsewhere it is a no-op.
Triple T{M.getTargetTriple()};
return T.isOSAIX();
}
PreservedAnalyses LowerCommentStringPass::run(Module &M,
ModuleAnalysisManager &AM) {
- if (DisableCopyrightMetadata || !isSupportedTarget(M))
+ if (DisableLowerCommentString || !isSupportedTarget(M))
return PreservedAnalyses::all();
LLVMContext &Ctx = M.getContext();
>From 8071563591298faf266c7bb1b2c6c535ab9461a6 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 28 Sep 2026 16:17:23 +0530
Subject: [PATCH 22/31] [Clang] Remove stray include of Attrs.inc from
SemaDecl.cpp
---
clang/lib/Sema/SemaDecl.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7acc826419ad5..b001207c0a02f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14,7 +14,6 @@
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTLambda.h"
-#include "clang/AST/Attrs.inc"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
>From 6773e9a1d0aaf225cd038330dd1db31aa1454380 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 13:22:42 +0530
Subject: [PATCH 23/31] [Clang][AIX] Diagnose inline variables named in
-mloadtime-comment-vars
---
clang/docs/LanguageExtensions.md | 41 +++++++------
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/lib/Sema/SemaDecl.cpp | 6 ++
.../PowerPC/loadtime-comment-vars-modules.cpp | 58 +++++++++++--------
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 15 ++---
clang/test/Sema/loadtime-comment-vars.cpp | 12 +++-
6 files changed, 74 insertions(+), 59 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 875b8b486a9da..b812a3a994a28 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6602,10 +6602,10 @@ Valid variable types:
A variable named in the list must meet all of these conditions to be
preserved:
-- It must be defined at file or namespace scope; `inline`, `constexpr`, and
- `constinit` variables are supported. A name-matched function-local `static`
- variable, static data member, or variable template specialization is not
- supported and is diagnosed.
+- It must be defined at file or namespace scope; `constexpr` and `constinit`
+ variables are supported. A name-matched function-local `static` variable,
+ static data member, `inline` variable, or variable template specialization
+ is not supported and is diagnosed.
- Its type must be a character pointer (`char *`, `const char *`) or a
character array (`char[]`, `const char[]`). The character type must be plain
`char`: variables of `signed char`, `unsigned char`, and the wide and
@@ -6626,17 +6626,17 @@ 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. The same applies to name-matched
-variables of unsupported kinds: function-local `static` variables, static data
-members, and variable template specializations (an explicit specialization is
-diagnosed at its definition, an implicit one in each translation unit that
-instantiates it). A name-matched variable of any other type -- for example, an
-`int` or a `struct` -- is likewise diagnosed. These diagnostics are in the
-`-Wloadtime-comment-var` group. A definition without an initializer is
-silently skipped. Names that match no variable defined in the translation unit
-are also silently ignored, since the option is typically given to every
-compilation of a build.
+dynamically initialized, is an `inline` variable, or is a pointer not bound to
+a string literal, is diagnosed with a warning and is not preserved. The same
+applies to name-matched variables of unsupported kinds: function-local
+`static` variables, static data members, and variable template
+specializations (an explicit specialization is diagnosed at its definition, an
+implicit one in each translation unit that instantiates it). A name-matched
+variable of any other type -- for example, an `int` or a `struct` -- is
+likewise diagnosed. These diagnostics are in the `-Wloadtime-comment-var`
+group. A definition without an initializer is silently skipped. Names that
+match no variable defined in the translation unit are also silently ignored,
+since the option is typically given to every compilation of a build.
For C++20 modules, a named variable defined in a module unit is processed
using the option as specified when the module unit itself is compiled -- in a
@@ -6644,12 +6644,11 @@ two-phase build, when the module interface file is produced. Giving the option
to an importing translation unit has no effect on variables owned by the
module. Retention of a variable to load time depends on the linker retaining
function definitions from the translation unit that was compiled to preserve
-it, since those functions carry the references that keep the string alive. An
-`inline` variable is preserved in the object files for the module unit and for
-every importing translation unit that re-emits it. A variable attached to a
-named module is matched by its module-attached mangled name (for example,
-`export char ver[];` in module `M` is `_ZW1M3ver`). The same applies to a
-precompiled header: the option must be present when the PCH is built.
+it, since those functions carry the references that keep the string alive. A
+variable attached to a named module is matched by its module-attached mangled
+name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
+same applies to a precompiled header: the option must be present when the PCH
+is built.
Example:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 57bdc9bd4e138..388aeea58e399 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3872,6 +3872,7 @@ def warn_loadtime_comment_var_not_preserved
"%FunctionLocal{is a function-local variable}|"
"%StaticDataMember{is a static data member}|"
"%TemplateSpecialization{is a variable template specialization}|"
+ "%Inline{is an inline variable}|"
"%UnsupportedType{does not have a plain char pointer or array type}"
"}1 and will not be preserved">,
InGroup<LoadtimeCommentVar>;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b001207c0a02f..68507f4b69e05 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15532,6 +15532,12 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
Reason = diag::LoadTimeCommentVarReason::TemplateSpecialization;
else if (VD->isStaticDataMember())
Reason = diag::LoadTimeCommentVarReason::StaticDataMember;
+ else if (VD->isInline())
+ // An inline variable is defined in every translation unit that sees it,
+ // and a module importer re-emits it using the decision made when the
+ // module was built, so the result would depend on option consistency
+ // across compilations.
+ Reason = diag::LoadTimeCommentVarReason::Inline;
else if (Pointee.isNull() ||
!S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy))
// Only plain `char` pointers/arrays are supported. A name match on a
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
index 0fb2d9975e11b..a808778c88021 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
@@ -1,14 +1,17 @@
-// Test -mloadtime-comment-vars= across a C++20 named-module boundary. Five
+// Test -mloadtime-comment-vars= across a C++20 named-module boundary. Six
// scenarios, each named by its FileCheck or -verify prefix:
//
// MOD — the module unit is built to a BMI with the option and then
// compiled to IR from the BMI (the two-phase flow build systems
-// use). Exported, module-linkage, internal-linkage, and inline
+// use). Exported, module-linkage, and internal-linkage
// variables are all preserved: the implicit attribute is
-// serialized, and the internal and inline variables reach
-// CodeGen via the module-initializer list. The same IR is
-// produced whether or not the option is repeated on the
-// codegen step: the BMI already records the result.
+// serialized, and the internal variable reaches CodeGen via
+// the module-initializer list. The same IR is produced whether
+// or not the option is repeated on the codegen step: the BMI
+// already records the result. The name-matched inline variable
+// is not preserved and, being unreferenced, is not emitted.
+// inline — the name-matched inline variable is diagnosed when the
+// module unit is compiled.
// NOOPT + — the same module unit built to a BMI without the option and
// NOOPTNOT compiled to IR with it: nothing is preserved. The option
// applies to the compilation of the module unit itself, where
@@ -18,8 +21,7 @@
// neither re-emitted nor preserved here, and the module-internal
// variable does not leak into the importer. The inline variable
// it references is re-emitted here as usual for inline
-// variables, and that copy is preserved as well because the
-// attribute travels with the declaration in the BMI.
+// variables, and that copy is not preserved either.
// verify — specializations instantiated here from the imported template
// definitions are diagnosed in this TU, at the pattern location
// in the module interface, with a note at the instantiation
@@ -32,9 +34,9 @@
// build _ZW1M5build module linkage: preserved likewise
// priv _ZL4priv internal linkage: preserved likewise; never
// emitted by an importing TU
-// iv _ZW1M2iv exported inline: preserved by the module unit
-// (emitted there although unreferenced) and by
-// every importer that references it
+// iv _ZW1M2iv exported inline: diagnosed in the module
+// unit; preserved neither there nor in an
+// importer that references it
// vt<int> _ZW1M2vtIiE instantiated in the importer: diagnosed there
// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: diagnosed there
@@ -45,9 +47,15 @@
// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv,_ZW1M2iv \
-// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD
+// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD \
+// RUN: --implicit-check-not=@_ZW1M2iv
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD
+// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD \
+// RUN: --implicit-check-not=@_ZW1M2iv
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZW1M2iv \
+// RUN: -fsyntax-only -verify=inline %t/m.cppm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -emit-module-interface %t/m.cppm -o %t/m-noopt.pcm
@@ -68,13 +76,13 @@
// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
// RUN: -fsyntax-only -verify %t/use.cpp
-// All four variables carry the metadata and are kept in llvm.compiler.used
-// when the module unit is compiled from a BMI that was built with the option.
+// The three non-inline variables carry the metadata and are kept in
+// llvm.compiler.used when the module unit is compiled from a BMI that was
+// built with the option. The inline variable is not among them.
// MOD-DAG: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1, !loadtime_comment ![[MD:[0-9]+]]
// MOD-DAG: @_ZW1M5build = global [18 x i8] c"@(#) module build\00", align 1, !loadtime_comment ![[MD]]
// MOD-DAG: @_ZL4priv = internal global [17 x i8] c"@(#) module priv\00", align 1, !loadtime_comment ![[MD]]
-// MOD-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8, !loadtime_comment ![[MD]]
-// MOD-DAG: @llvm.compiler.used = appending global [4 x ptr]
+// MOD-DAG: @llvm.compiler.used = appending global [3 x ptr]
// Without the option at BMI-build time nothing is preserved: the exported
// variable is an ordinary global (the {{$}} anchor proves no metadata), and
@@ -85,14 +93,14 @@
// NOOPTNOT-NOT: @_ZL4priv
// NOOPTNOT-NOT: @_ZW1M2iv
-// The importer instantiates the templates (ordinary linkonce_odr definitions,
-// no metadata — the {{$}} anchor proves it) and re-emits the inline variable
-// it references, with the metadata carried over from the BMI; that copy is
-// the only entry in its llvm.compiler.used. It emits neither the module-owned
-// non-inline variable it names nor the module-internal one.
+// The importer instantiates the templates and re-emits the inline variable
+// it references (ordinary linkonce_odr definitions, no metadata — the {{$}}
+// anchor proves it), so nothing is preserved here. It emits neither the
+// module-owned non-inline variable it names nor the module-internal one.
// IMPORT-DAG: @_ZW1M2vtIiE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
-// IMPORT-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8, !loadtime_comment !{{[0-9]+}}
-// IMPORT: @llvm.compiler.used = appending global [1 x ptr] [ptr @_ZW1M2iv]
+// IMPORT-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8{{$}}
+// IMPORTNOT-NOT: !loadtime_comment
+// IMPORTNOT-NOT: @llvm.compiler.used
// IMPORTNOT-NOT: @_ZW1M3ver
// IMPORTNOT-NOT: @_ZW1M5build
// IMPORTNOT-NOT: @_ZL4priv
@@ -102,7 +110,7 @@ export module M;
export char ver[] = "@(#) module ver";
char build[] = "@(#) module build";
static char priv[] = "@(#) module priv";
-export inline const char *iv = "@(#) module inline";
+export inline const char *iv = "@(#) module inline"; // inline-warning {{'iv' named in '-mloadtime-comment-vars=' is an inline variable and will not be preserved}}
export template <class T> const char *vt = "@(#) vt";
export template <class T> struct S { static const char *m; };
template <class T> const char *S<T>::m = "@(#) sdm";
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 602a75d90b809..12b621ff36b14 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -40,7 +40,6 @@
// version asmid preserved (asm label)
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
-// sccsid_inl sccsid_inl preserved (inline variable, linkonce_odr)
// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
// DecompositionDecl owns the storage)
//
@@ -56,7 +55,7 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,sccsid_inl,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -130,7 +129,6 @@ static char8_t u8str[] = u8"@(#) u8";
// what materializes the string in the object.
static constexpr const char *sccsid_ce = "@(#) constexpr";
constinit const char *sccsid_ci = "@(#) constinit";
-inline const char *sccsid_inl = "@(#) inline";
// 10. Structured binding. The bindings a/b/c are BindingDecls with no storage
// of their own; the hidden DecompositionDecl (a VarDecl) owns the array,
@@ -217,25 +215,22 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @_ZN12_GLOBAL__N_14anonE = internal global [10 x i8] c"@(#) anon\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @asmid = global [15 x i8] c"@(#) asm label\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// Eligible C++ forms: static constexpr (internal, constant), constinit
-// (external), and inline (linkonce_odr) are all preserved.
+// Eligible C++ forms: static constexpr (internal, constant) and constinit
+// (external) are both preserved.
// CHECK-DAG: @_ZL9sccsid_ce = internal constant ptr @[[CE_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[CE_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constexpr\00", align {{[0-9]+}}
// CHECK-DAG: @sccsid_ci = global ptr @[[CI_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[CI_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constinit\00", align {{[0-9]+}}
-// CHECK-DAG: @sccsid_inl = linkonce_odr global ptr @[[INL_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// CHECK-DAG: @[[INL_STR]] = private unnamed_addr constant [12 x i8] c"@(#) inline\00", align {{[0-9]+}}
// CHECK-DAG: @_ZDC1a1b1cE = internal constant [3 x i8] c"ab\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// The ten supported matched globals are preserved in llvm.compiler.used;
+// The nine supported matched globals are preserved in llvm.compiler.used;
// the two static data members are not.
-// CHECK: @llvm.compiler.used = appending global [10 x ptr]
+// CHECK: @llvm.compiler.used = appending global [9 x ptr]
// CHECK-SAME: @x
// CHECK-SAME: @_ZN1N1xE
// CHECK-SAME: @_ZN1NL3ptrE
// CHECK-SAME: @_ZL9sccsid_ce
// CHECK-SAME: @sccsid_ci
-// CHECK-SAME: @sccsid_inl
// CHECK-SAME: @_ZDC1a1b1cE
// CHECK-SAME: @cver
// CHECK-SAME: @_ZN12_GLOBAL__N_14anonE
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
index d72c823c3a9f7..9796034d27c03 100644
--- a/clang/test/Sema/loadtime-comment-vars.cpp
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -15,8 +15,9 @@
// literal), are diagnosed. (-std=c++20 for consteval.)
// kinds — unsupported declaration kinds: name-matched static data
// members (out-of-line, in-class inline, instantiated from a
-// class template) and variable template specializations
-// (explicit and implicit) are diagnosed.
+// class template), variable template specializations
+// (explicit and implicit), and namespace-scope inline variables
+// are diagnosed.
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn,_ZL4wstr \
@@ -27,7 +28,7 @@
// RUN: -fsyntax-only -verify=init %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZN2B23sidE,_ZN2B34isidE,_Z2vtIcE,_Z2vtIiE,_ZN2SCIiE1mE \
+// RUN: -mloadtime-comment-vars=_ZN2B23sidE,_ZN2B34isidE,_Z2vtIcE,_Z2vtIiE,_ZN2SCIiE1mE,_ZN1M2ivE \
// RUN: -fsyntax-only -verify=kinds %s
// ---- storage: storage-duration cases ----------------------------------------
@@ -101,6 +102,11 @@ struct B2 {
};
const char *B2::sid = "@(#) b2"; // kinds-warning {{'sid' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+// A namespace-scope inline variable is diagnosed.
+namespace M {
+inline const char *iv = "@(#) inline var"; // kinds-warning {{'iv' named in '-mloadtime-comment-vars=' is an inline variable and will not be preserved}}
+} // namespace M
+
// An in-class inline static data member (C++17) is diagnosed as well.
struct B3 {
static inline const char *isid = "@(#) b3"; // kinds-warning {{'isid' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
>From 2bd77e98ece639e69a61f15edb2c453ac54fb0e4 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 20:23:27 +0530
Subject: [PATCH 24/31] [Clang][NFC] Remove the
LangOptions::isLoadTimeCommentVar accessor
---
clang/include/clang/Basic/LangOptions.h | 3 ---
clang/lib/Basic/LangOptions.cpp | 4 ----
clang/lib/Sema/SemaDecl.cpp | 4 ++--
3 files changed, 2 insertions(+), 9 deletions(-)
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 1322695c3c899..874011bea48a1 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -705,9 +705,6 @@ class LangOptions : public LangOptionsBase {
/// builtin because a -fno-builtin-* option has been specified?
bool isNoBuiltinFunc(StringRef Name) const;
- /// Returns true if \p MangledName is listed in -mloadtime-comment-vars=.
- bool isLoadTimeCommentVar(StringRef MangledName) const;
-
/// True if any ObjC types may have non-trivial lifetime qualifiers.
bool allowsNonTrivialObjCLifetimeQualifiers() const {
return ObjCAutoRefCount || ObjCWeak;
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index f61df3483c483..69b9176801aea 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -57,10 +57,6 @@ bool LangOptions::isNoBuiltinFunc(StringRef FuncName) const {
return false;
}
-bool LangOptions::isLoadTimeCommentVar(StringRef MangledName) const {
- return llvm::is_contained(LoadTimeCommentVars, MangledName);
-}
-
VersionTuple LangOptions::getOpenCLVersionTuple() const {
const int Ver = OpenCLCPlusPlus ? OpenCLCPlusPlusVersion : OpenCLVersion;
if (OpenCLCPlusPlus && Ver != 100)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 68507f4b69e05..19347606cfacc 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15517,8 +15517,8 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
if (!S.LoadTimeCommentVarNameGenerator)
S.LoadTimeCommentVarNameGenerator =
std::make_unique<ASTNameGenerator>(S.Context);
- if (!S.getLangOpts().isLoadTimeCommentVar(
- S.LoadTimeCommentVarNameGenerator->getName(VD)))
+ if (!llvm::is_contained(S.getLangOpts().LoadTimeCommentVars,
+ S.LoadTimeCommentVarNameGenerator->getName(VD)))
return;
std::optional<unsigned> Reason;
>From ed2eadc13996025ab99cf097854376fd291e2544 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 20:23:29 +0530
Subject: [PATCH 25/31] [Clang][AIX] Diagnose alias variables named in
-mloadtime-comment-vars
---
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/lib/Sema/SemaDecl.cpp | 10 ++++++--
.../PowerPC/loadtime-comment-vars-alias.c | 24 +++++++++++++++++++
clang/test/Sema/loadtime-comment-vars.c | 12 +++++++++-
4 files changed, 44 insertions(+), 3 deletions(-)
create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-vars-alias.c
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 388aeea58e399..b4caffcb7a3ac 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3873,6 +3873,7 @@ def warn_loadtime_comment_var_not_preserved
"%StaticDataMember{is a static data member}|"
"%TemplateSpecialization{is a variable template specialization}|"
"%Inline{is an inline variable}|"
+ "%Alias{is defined as an alias}|"
"%UnsupportedType{does not have a plain char pointer or array type}"
"}1 and will not be preserved">,
InGroup<LoadtimeCommentVar>;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 19347606cfacc..5d21ecf6fd3f7 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15482,8 +15482,10 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
return;
// A definition without an initializer carries no string and is silently
- // skipped.
- if (!VD->hasInit())
+ // skipped. An alias is the exception: it is a definition without an
+ // initializer, but a name match on it demonstrates intent, so it is
+ // diagnosed below.
+ if (!VD->hasInit() && !VD->hasAttr<AliasAttr>())
return;
// Extract the character type a pointer points to or an array holds; it is
@@ -15538,6 +15540,10 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// module was built, so the result would depend on option consistency
// across compilations.
Reason = diag::LoadTimeCommentVarReason::Inline;
+ else if (VD->hasAttr<AliasAttr>())
+ // An alias has no storage of its own; the string belongs to the
+ // aliasee, which is the variable that has to be named.
+ Reason = diag::LoadTimeCommentVarReason::Alias;
else if (Pointee.isNull() ||
!S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy))
// Only plain `char` pointers/arrays are supported. A name match on a
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-alias.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-alias.c
new file mode 100644
index 0000000000000..1e3fb812d4a8f
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-alias.c
@@ -0,0 +1,24 @@
+// Test -mloadtime-comment-vars= with a variable defined as an alias. Both the
+// alias and the aliased variable are named in the list:
+// * the aliased variable owns the string, so it gets !loadtime_comment
+// metadata and is kept alive in llvm.compiler.used;
+// * the alias is diagnosed (covered by the Sema tests; silenced here with
+// -Wno-loadtime-comment-var) and is emitted as an ordinary alias, without
+// metadata.
+
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -Wno-loadtime-comment-var \
+// RUN: -mloadtime-comment-vars=sccsid,real -emit-llvm -disable-llvm-passes \
+// RUN: -o - %s | FileCheck %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
+// RUN: -mloadtime-comment-vars=sccsid,real -emit-llvm -disable-llvm-passes \
+// RUN: -o - %s | FileCheck %s
+
+static char real[] = "@(#) real string";
+extern char sccsid[17] __attribute__((alias("real")));
+
+void foo(void) {}
+
+// CHECK: @real = internal global [17 x i8] c"@(#) real string\00", align 1, !loadtime_comment ![[MD:[0-9]+]]
+// CHECK: @llvm.compiler.used = appending global [1 x ptr] [ptr @real], section "llvm.metadata"
+// CHECK: @sccsid = alias [17 x i8], ptr @real{{$}}
+// CHECK: ![[MD]] = !{}
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index 3a4b1811f8000..72d23574be324 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -9,6 +9,8 @@
// - thread-local variable (__thread, not static storage duration)
// - pointer not initialized directly with a string literal
// - name-matched variables of an unsupported type (int, unsigned char)
+// - variables defined as an alias (array and pointer) are diagnosed, while
+// naming the aliased variable is accepted without diagnostic
// - valid const char array — no diagnostic
// - a preserved variable counts as used: no -Wunused-const-variable for it,
// while an unlisted static const variable still warns
@@ -17,7 +19,7 @@
// is rejected with an error.
// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept,notchar,ustr \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept,notchar,ustr,alias_arr,alias_ptr,aliasee_arr \
// RUN: -fsyntax-only -verify %s
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu \
@@ -62,3 +64,11 @@ static const char dropped[] = "@(#) dropped"; // expected-warning {{unused varia
// it is diagnosed rather than silently ignored.
static int notchar = 42; // expected-warning {{'notchar' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
static unsigned char ustr[] = "@(#) u"; // expected-warning {{'ustr' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
+
+// A variable defined as an alias has no storage of its own and is diagnosed.
+// The aliased variable is the one to name: aliasee_arr is listed and accepted
+// without diagnostic.
+char aliasee_arr[] = "@(#) aliasee arr";
+extern char alias_arr[17] __attribute__((alias("aliasee_arr"))); // expected-warning {{'alias_arr' named in '-mloadtime-comment-vars=' is defined as an alias and will not be preserved}}
+const char *aliasee_ptr = "@(#) aliasee ptr";
+extern const char *alias_ptr __attribute__((alias("aliasee_ptr"))); // expected-warning {{'alias_ptr' named in '-mloadtime-comment-vars=' is defined as an alias and will not be preserved}}
>From 32f55740734a7e91e4304feb946c31d59064e063 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 20:23:31 +0530
Subject: [PATCH 26/31] [Clang][AIX][Docs] Restructure the
-mloadtime-comment-vars documentation
---
clang/docs/LanguageExtensions.md | 65 ++++++++++++++++++--------------
1 file changed, 37 insertions(+), 28 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index b812a3a994a28..b605a9bf3a104 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6575,9 +6575,9 @@ Syntax:
The option can be repeated; the names from all occurrences are combined.
-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 internal-linkage variable (for
+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 internal-linkage variable (for
example, a file-scope `static`) must be named using its mangled form:
```c++
@@ -6624,31 +6624,26 @@ preserved:
compiled from the defining translation unit itself, not merely in the final
linked output.
-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, is an `inline` variable, or is a pointer not bound to
-a string literal, is diagnosed with a warning and is not preserved. The same
-applies to name-matched variables of unsupported kinds: function-local
-`static` variables, static data members, and variable template
-specializations (an explicit specialization is diagnosed at its definition, an
-implicit one in each translation unit that instantiates it). A name-matched
-variable of any other type -- for example, an `int` or a `struct` -- is
-likewise diagnosed. These diagnostics are in the `-Wloadtime-comment-var`
-group. A definition without an initializer is silently skipped. Names that
-match no variable defined in the translation unit are also silently ignored,
-since the option is typically given to every compilation of a build.
-
-For C++20 modules, a named variable defined in a module unit is processed
-using the option as specified when the module unit itself is compiled -- in a
-two-phase build, when the module interface file is produced. Giving the option
-to an importing translation unit has no effect on variables owned by the
-module. Retention of a variable to load time depends on the linker retaining
-function definitions from the translation unit that was compiled to preserve
-it, since those functions carry the references that keep the string alive. A
-variable attached to a named module is matched by its module-attached mangled
-name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
-same applies to a precompiled header: the option must be present when the PCH
-is built.
+A variable that is named in the list is diagnosed with a warning in the
+`-Wloadtime-comment-var` group, and is not preserved, if it:
+
+- is `volatile`-qualified;
+- does not have static storage duration (for example, a `thread_local`
+ variable);
+- is dynamically initialized;
+- is a pointer not bound to a string literal;
+- is an `inline` variable;
+- is defined as an alias with `__attribute__((alias("...")))` (the string
+ belongs to the aliased variable, which is the one to name instead);
+- is a function-local `static` variable, a static data member, or a variable
+ template specialization (an explicit specialization is diagnosed at its
+ definition, an implicit one in each translation unit that instantiates it);
+- has any type other than a plain `char` pointer or array -- for example, an
+ `int` or a `struct`.
+
+A definition without an initializer, other than an alias, is silently skipped.
+Names that match no variable defined in the translation unit are also silently
+ignored, since the option is typically given to every compilation of a build.
Example:
@@ -6676,6 +6671,20 @@ source.o:
Built 2026-05-24
```
+### Interaction with C++20 Modules and Precompiled Headers
+
+For C++20 modules, a named variable defined in a module unit is processed
+using the option as specified when the module unit itself is compiled -- in a
+two-phase build, when the module interface file is produced. Giving the option
+to an importing translation unit has no effect on variables owned by the
+module. Retention of a variable to load time depends on the linker retaining
+function definitions from the translation unit that was compiled to preserve
+it, since those functions carry the references that keep the string alive. A
+variable attached to a named module is matched by its module-attached mangled
+name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
+same applies to a precompiled header: the option must be present when the PCH
+is built.
+
### Interaction with `#pragma comment(copyright, ...)`
The two mechanisms can be used together in the same translation unit. The
>From 3d1de81a96483d095c50c151ce76939b6da997d0 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 21:22:24 +0530
Subject: [PATCH 27/31] [Clang][AIX] Diagnose header-unit variables for
-mloadtime-comment-vars and address review comments
---
clang/docs/LanguageExtensions.md | 12 ++-
.../clang/Basic/DiagnosticSemaKinds.td | 1 +
clang/include/clang/Sema/Sema.h | 1 +
clang/lib/Sema/SemaDecl.cpp | 41 +++++-----
.../PowerPC/loadtime-comment-vars-modules.cpp | 74 ++++++++++++++++++-
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 26 ++-----
clang/test/Driver/mloadtime-comment-vars.c | 15 ++--
clang/test/Sema/loadtime-comment-vars.cpp | 4 +-
8 files changed, 118 insertions(+), 56 deletions(-)
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index b605a9bf3a104..3b606198daeb5 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6635,6 +6635,7 @@ A variable that is named in the list is diagnosed with a warning in the
- is an `inline` variable;
- is defined as an alias with `__attribute__((alias("...")))` (the string
belongs to the aliased variable, which is the one to name instead);
+- is defined in a C++20 header unit;
- is a function-local `static` variable, a static data member, or a variable
template specialization (an explicit specialization is diagnosed at its
definition, an implicit one in each translation unit that instantiates it);
@@ -6681,9 +6682,14 @@ module. Retention of a variable to load time depends on the linker retaining
function definitions from the translation unit that was compiled to preserve
it, since those functions carry the references that keep the string alive. A
variable attached to a named module is matched by its module-attached mangled
-name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). The
-same applies to a precompiled header: the option must be present when the PCH
-is built.
+name (for example, `export char ver[];` in module `M` is `_ZW1M3ver`). A
+variable defined in a header that is included in the global module fragment of
+a module unit is preserved in the object file of that module unit. A header
+unit, in contrast, is not compiled to an object file of its own, so a named
+variable defined in a header unit is diagnosed and is not preserved.
+
+The same applies to a precompiled header as to a module unit: the option must
+be present when the PCH is built.
### Interaction with `#pragma comment(copyright, ...)`
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index b4caffcb7a3ac..8d12ce24c3da0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3874,6 +3874,7 @@ def warn_loadtime_comment_var_not_preserved
"%TemplateSpecialization{is a variable template specialization}|"
"%Inline{is an inline variable}|"
"%Alias{is defined as an alias}|"
+ "%HeaderUnit{is defined in a header unit}|"
"%UnsupportedType{does not have a plain char pointer or array type}"
"}1 and will not be preserved">,
InGroup<LoadtimeCommentVar>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7ce90d58c55e3..d8b8ecfdaefe9 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4199,6 +4199,7 @@ class Sema final : public SemaBase {
/// Exposed for instantiated variable definitions, which do not pass
/// through FinalizeDeclaration.
void ProcessLoadTimeCommentVar(VarDecl *VD);
+
/// Lazily created mangler for '-mloadtime-comment-vars=' name matching. Only
/// allocated if the option is in use.
std::unique_ptr<ASTNameGenerator> LoadTimeCommentVarNameGenerator;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5d21ecf6fd3f7..eeebfc87526a2 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15488,16 +15488,6 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
if (!VD->hasInit() && !VD->hasAttr<AliasAttr>())
return;
- // Extract the character type a pointer points to or an array holds; it is
- // null for any other type, which is classified (and diagnosed) below once
- // the name has matched.
- QualType Ty = VD->getType();
- const PointerType *PT = Ty->getAsCanonical<PointerType>();
- const ArrayType *AT = PT ? nullptr : S.Context.getAsArrayType(Ty);
- QualType Pointee = PT ? PT->getPointeeType()
- : AT ? AT->getElementType()
- : QualType();
-
// Mangling is comparatively expensive, so first check cheaply whether the
// source identifier appears in any listed name at all: both the Itanium
// mangling and an unmangled C name embed the identifier verbatim. A
@@ -15523,8 +15513,23 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
S.LoadTimeCommentVarNameGenerator->getName(VD)))
return;
+ // Extract the character type a pointer points to or an array holds; it is
+ // null for any other type, which is classified (and diagnosed) below.
+ QualType Ty = VD->getType();
+ const PointerType *PT = Ty->getAsCanonical<PointerType>();
+ const ArrayType *AT = PT ? nullptr : S.Context.getAsArrayType(Ty);
+ QualType Pointee = PT ? PT->getPointeeType()
+ : AT ? AT->getElementType()
+ : QualType();
+
+ Module *Mod = S.getCurrentModule();
std::optional<unsigned> Reason;
- if (VD->isLocalVarDecl())
+
+ if (VD->getStorageDuration() != SD_Static)
+ // The string must have static storage duration; a thread-local variable
+ // is not preserved.
+ Reason = diag::LoadTimeCommentVarReason::BadStorage;
+ else if (VD->isLocalVarDecl())
// Only file- and namespace-scope variables are supported. A name match
// on anything else demonstrates intent (scope participates in the
// mangled name), so the unsupported kinds are diagnosed rather than
@@ -15544,6 +15549,11 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// An alias has no storage of its own; the string belongs to the
// aliasee, which is the variable that has to be named.
Reason = diag::LoadTimeCommentVarReason::Alias;
+ else if (Mod && Mod->isHeaderUnit())
+ // A header unit is not compiled to an object file of its own; a variable
+ // defined in one is only emitted by the translation units that import
+ // it, using the decision made when the header unit was built.
+ Reason = diag::LoadTimeCommentVarReason::HeaderUnit;
else if (Pointee.isNull() ||
!S.Context.hasSameUnqualifiedType(Pointee, S.Context.CharTy))
// Only plain `char` pointers/arrays are supported. A name match on a
@@ -15551,10 +15561,6 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// signed/unsigned character types, ...) still demonstrates intent, so it
// is diagnosed.
Reason = diag::LoadTimeCommentVarReason::UnsupportedType;
- else if (VD->getStorageDuration() != SD_Static)
- // The string must have static storage duration; a thread-local variable
- // is not preserved.
- Reason = diag::LoadTimeCommentVarReason::BadStorage;
else if (Ty.isVolatileQualified() || Pointee.isVolatileQualified())
// The intended usage does not intersect with use cases where the character
// array or the pointer to it is volatile-qualified; such variables are not
@@ -15585,9 +15591,8 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// module-initializer lists, and CheckCompleteVariableDeclaration records
// only non-discardable variables there. Record an internal-linkage variable
// so its definition reaches CodeGen in that compilation.
- if (Module *M = S.getCurrentModule())
- if (isDiscardableGVALinkage(S.Context.GetGVALinkageForVariable(VD)))
- S.Context.addModuleInitializer(M, VD);
+ if (Mod && isDiscardableGVALinkage(S.Context.GetGVALinkageForVariable(VD)))
+ S.Context.addModuleInitializer(Mod, VD);
}
void Sema::ProcessLoadTimeCommentVar(VarDecl *VD) {
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
index a808778c88021..4514fa9ce7afa 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
@@ -1,5 +1,5 @@
-// Test -mloadtime-comment-vars= across a C++20 named-module boundary. Six
-// scenarios, each named by its FileCheck or -verify prefix:
+// Test -mloadtime-comment-vars= across C++20 module boundaries. The
+// scenarios are named by their FileCheck or -verify prefixes:
//
// MOD — the module unit is built to a BMI with the option and then
// compiled to IR from the BMI (the two-phase flow build systems
@@ -26,6 +26,15 @@
// definitions are diagnosed in this TU, at the pattern location
// in the module interface, with a note at the instantiation
// point.
+// GMF + — a module unit whose global module fragment includes a header
+// GMFIMPORT defining internal-linkage variables, built to a BMI with the
+// option and then compiled to IR from the BMI: the header's
+// variables are preserved in the module unit's object file.
+// An importing TU does not emit them.
+// hu + — the same header built as a header unit: a header unit has no
+// HUIMPORT object file of its own, so the name-matched variables are
+// diagnosed, and an importing TU that emits one does not
+// preserve it.
//
// Source IR symbol Expected treatment
// ------ --------- ------------------
@@ -39,6 +48,9 @@
// importer that references it
// vt<int> _ZW1M2vtIiE instantiated in the importer: diagnosed there
// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: diagnosed there
+// hdrid _ZL5hdrid defined in ident.h: preserved by a module unit
+// hdrptr _ZL6hdrptr that includes the header in its global module
+// fragment; diagnosed in a header unit
// RUN: split-file %s %t
@@ -76,6 +88,24 @@
// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
// RUN: -fsyntax-only -verify %t/use.cpp
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
+// RUN: -emit-module-interface %t/gmf.cppm -o %t/gmf.pcm
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -emit-llvm %t/gmf.pcm -o - | FileCheck %s --check-prefix=GMF
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -fmodule-file=G=%t/gmf.pcm -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
+// RUN: -emit-llvm %t/use-gmf.cpp -o - | FileCheck %s --check-prefix=GMFIMPORT
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
+// RUN: -emit-header-unit -xc++-user-header %t/ident.h -o %t/ident.pcm \
+// RUN: -verify=hu
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -Wno-experimental-header-units -fmodule-file=%t/ident.pcm \
+// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
+// RUN: -emit-llvm %t/use-hu.cpp -o - | FileCheck %s --check-prefix=HUIMPORT
+
// The three non-inline variables carry the metadata and are kept in
// llvm.compiler.used when the module unit is compiled from a BMI that was
// built with the option. The inline variable is not among them.
@@ -105,6 +135,25 @@
// IMPORTNOT-NOT: @_ZW1M5build
// IMPORTNOT-NOT: @_ZL4priv
+// The internal-linkage variables from the header included in the global
+// module fragment carry the metadata and are kept in llvm.compiler.used when
+// the module unit is compiled from its BMI.
+// GMF-DAG: @_ZL5hdrid = internal global [15 x i8] c"@(#) header id\00", align 1, !loadtime_comment ![[GMD:[0-9]+]]
+// GMF-DAG: @_ZL6hdrptr = internal global ptr @{{.*}}, align 8, !loadtime_comment ![[GMD]]
+// GMF-DAG: @llvm.compiler.used = appending global [2 x ptr]
+
+// An importer of that module emits neither variable.
+// GMFIMPORT-NOT: @_ZL5hdrid
+// GMFIMPORT-NOT: @_ZL6hdrptr
+// GMFIMPORT-NOT: !loadtime_comment
+
+// An importer of the header unit emits the variable it references as an
+// ordinary definition (the {{$}} anchor proves no metadata) and preserves
+// nothing.
+// HUIMPORT: @_ZL6hdrptr = internal global ptr @{{.*}}, align 8{{$}}
+// HUIMPORT-NOT: !loadtime_comment
+// HUIMPORT-NOT: @llvm.compiler.used
+
//--- m.cppm
export module M;
export char ver[] = "@(#) module ver";
@@ -123,3 +172,24 @@ const char *u2 = S<int>::m; // expected-note {{in instantiation of static data m
const char *u3 = iv;
// expected-warning at m.cppm:6 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
// expected-warning at m.cppm:8 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+
+//--- ident.h
+#ifndef IDENT_H
+#define IDENT_H
+static char hdrid[] = "@(#) header id"; // hu-warning {{'hdrid' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
+static const char *hdrptr = "@(#) header ptr"; // hu-warning {{'hdrptr' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
+#endif
+
+//--- gmf.cppm
+module;
+#include "ident.h"
+export module G;
+export inline void touch_g() {}
+
+//--- use-gmf.cpp
+import G;
+void use_g() { touch_g(); }
+
+//--- use-hu.cpp
+import "ident.h";
+const char *use_hu() { return hdrptr; }
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 12b621ff36b14..d2bf5329c46aa 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -1,4 +1,4 @@
-// Test -mloadtime-comment-vars= IR output for C++ on AIX. Four scenarios
+// Test -mloadtime-comment-vars= IR output for C++ on AIX. Three scenarios
// are covered, each with its own set of named variables and check prefix:
//
// CHECK — mangled-name matching: file- and namespace-scope variables
@@ -9,7 +9,8 @@
// proves that listed variables of non-plain-char element type
// (wchar_t, char16_t, char8_t) and of non-character type are
// not preserved and not emitted. Their diagnostic is covered
-// by the Sema tests and silenced here.
+// by the Sema tests and silenced here. The names are split
+// across two occurrences of the option, which are combined.
//
// STORAGE — storage-duration filtering: thread_local variables are
// diagnosed by Sema and receive no metadata; a function-local
@@ -17,9 +18,6 @@
//
// SPACE/DUP — list-parsing edge cases: a name with a leading space matches
// nothing; a duplicate name preserves the variable exactly once.
-// MULTI — multiple occurrences of the option on the command line combine
-// the lists; cc1 receives every occurrence in order and combines
-// them internally.
//
// Names used in the matching scenario:
//
@@ -55,7 +53,8 @@
// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
-// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr,_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
+// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr \
+// RUN: -mloadtime-comment-vars=_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -72,10 +71,6 @@
// RUN: -mloadtime-comment-vars=foo,foo \
// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=DUP
-// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=foo -mloadtime-comment-vars=bar \
-// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=MULTI
-
// ===========================================================================
// Mangled-name matching
// ===========================================================================
@@ -273,14 +268,3 @@ char bar[] = "@(#) bar";
// 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"
-
-// ===========================================================================
-// MULTI option patterns — the option given more than once
-// ===========================================================================
-
-// The option given twice: the lists are combined, so both are preserved.
-// MULTI-DAG: @foo = global [9 x i8] c"@(#) foo\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
-// MULTI-DAG: @bar = global [9 x i8] c"@(#) bar\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
-// MULTI: @llvm.compiler.used = appending global [2 x ptr]
-// MULTI-SAME: @foo
-// MULTI-SAME: @bar
diff --git a/clang/test/Driver/mloadtime-comment-vars.c b/clang/test/Driver/mloadtime-comment-vars.c
index d1260c1bbc906..7bc42c424dd50 100644
--- a/clang/test/Driver/mloadtime-comment-vars.c
+++ b/clang/test/Driver/mloadtime-comment-vars.c
@@ -2,27 +2,22 @@
// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid,version %s 2>&1 | FileCheck %s --check-prefix=NONAIX
// RUN: %clang -### -target powerpc-ibm-aix -mloadtime-comment-vars=sccsid -mloadtime-comment-vars=version,build %s 2>&1 | FileCheck %s --check-prefix=MULTI
-// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid -mloadtime-comment-vars=version %s 2>&1 | FileCheck %s --check-prefix=NONAIX-MULTI
+// RUN: %clang -### -target x86_64-linux-gnu -mloadtime-comment-vars=sccsid -mloadtime-comment-vars=version %s 2>&1 | FileCheck %s --check-prefix=NONAIX
// Verify the option is forwarded verbatim to cc1 for a supported target.
// CHECK: "-cc1"
// CHECK-SAME: "-mloadtime-comment-vars=sccsid,version"
// Verify a warning is emitted and the option is NOT forwarded for an
-// unsupported target.
+// unsupported target. When the option is repeated, the warning is emitted
+// only once and no occurrence is forwarded: the option must not be mentioned
+// again anywhere after the first warning.
// NONAIX: warning: ignoring '-mloadtime-comment-vars=' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
-// NONAIX: "-cc1"
-// NONAIX-NOT: "-mloadtime-comment-vars=sccsid,version"
+// NONAIX-NOT: -mloadtime-comment-vars=
// Verify every occurrence is forwarded, in order; cc1 combines the lists.
// MULTI: "-cc1"
// MULTI-SAME: "-mloadtime-comment-vars=sccsid"
// MULTI-SAME: "-mloadtime-comment-vars=version,build"
-// Verify a repeated option on an unsupported target warns once and forwards
-// nothing.
-// NONAIX-MULTI: warning: ignoring '-mloadtime-comment-vars=' option as it is not currently supported for target 'x86_64-unknown-linux-gnu'
-// NONAIX-MULTI: "-cc1"
-// NONAIX-MULTI-NOT: "-mloadtime-comment-vars=
-
int main(void) { return 0; }
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
index 9796034d27c03..81604c7a30eaf 100644
--- a/clang/test/Sema/loadtime-comment-vars.cpp
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -49,8 +49,8 @@ static thread_local const char *stl = "@(#) stl"; // storage-warning {{'stl' nam
struct A {
static thread_local const char *tm;
};
-// The static-data-member reason outranks the storage-duration one.
-thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+// The storage-duration reason outranks the static-data-member one.
+thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
// Function-local static: a name match demonstrates intent, so it is
// diagnosed rather than silently ignored.
>From a1e27f48bf69875b731453f6f3b2aa726765bec7 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 5 Oct 2026 21:32:21 +0530
Subject: [PATCH 28/31] [Clang][AIX] Move the -mloadtime-comment-vars module
diagnostics to a Sema test and tighten the CodeGen module checks
---
.../PowerPC/loadtime-comment-vars-modules.cpp | 109 +++++++++---------
.../Sema/loadtime-comment-vars-modules.cpp | 54 +++++++++
2 files changed, 106 insertions(+), 57 deletions(-)
create mode 100644 clang/test/Sema/loadtime-comment-vars-modules.cpp
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
index 4514fa9ce7afa..6c2d4b236527f 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp
@@ -1,5 +1,8 @@
-// Test -mloadtime-comment-vars= across C++20 module boundaries. The
-// scenarios are named by their FileCheck or -verify prefixes:
+// Test -mloadtime-comment-vars= IR output across C++20 module boundaries. The
+// diagnostics for the variables that are not preserved are covered by
+// clang/test/Sema/loadtime-comment-vars-modules.cpp and are silenced here
+// with -Wno-loadtime-comment-var. The scenarios are named by their FileCheck
+// prefixes:
//
// MOD — the module unit is built to a BMI with the option and then
// compiled to IR from the BMI (the two-phase flow build systems
@@ -10,31 +13,29 @@
// or not the option is repeated on the codegen step: the BMI
// already records the result. The name-matched inline variable
// is not preserved and, being unreferenced, is not emitted.
-// inline — the name-matched inline variable is diagnosed when the
-// module unit is compiled.
// NOOPT + — the same module unit built to a BMI without the option and
// NOOPTNOT compiled to IR with it: nothing is preserved. The option
// applies to the compilation of the module unit itself, where
// semantic analysis runs.
-// IMPORT + — an importing TU naming the module-owned variable: the
-// IMPORTNOT variable is defined in the module unit, not here, so it is
-// neither re-emitted nor preserved here, and the module-internal
-// variable does not leak into the importer. The inline variable
-// it references is re-emitted here as usual for inline
-// variables, and that copy is not preserved either.
-// verify — specializations instantiated here from the imported template
-// definitions are diagnosed in this TU, at the pattern location
-// in the module interface, with a note at the instantiation
-// point.
+// IMPORT + — an importing TU that names the module-owned variable in the
+// IMPORTNOT option and references it: the variable is defined in the
+// module unit, not here, so it is only declared here and is not
+// preserved, and the module-internal variable does not leak
+// into the importer. The inline variable it references is
+// re-emitted here as usual for inline variables, and that copy
+// is not preserved either. The
+// name-matched specializations it instantiates from the
+// imported templates (a variable template and a static data
+// member of a class template) are emitted but not preserved.
// GMF + — a module unit whose global module fragment includes a header
// GMFIMPORT defining internal-linkage variables, built to a BMI with the
// option and then compiled to IR from the BMI: the header's
// variables are preserved in the module unit's object file.
// An importing TU does not emit them.
-// hu + — the same header built as a header unit: a header unit has no
-// HUIMPORT object file of its own, so the name-matched variables are
-// diagnosed, and an importing TU that emits one does not
-// preserve it.
+// HUIMPORT — the same header built as a header unit: a header unit has no
+// object file of its own, so the name-matched variables are
+// not preserved, and an importing TU that emits one does not
+// preserve it either.
//
// Source IR symbol Expected treatment
// ------ --------- ------------------
@@ -43,18 +44,18 @@
// build _ZW1M5build module linkage: preserved likewise
// priv _ZL4priv internal linkage: preserved likewise; never
// emitted by an importing TU
-// iv _ZW1M2iv exported inline: diagnosed in the module
-// unit; preserved neither there nor in an
-// importer that references it
-// vt<int> _ZW1M2vtIiE instantiated in the importer: diagnosed there
-// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: diagnosed there
+// iv _ZW1M2iv exported inline: preserved neither in the
+// module unit nor in an importer that
+// references it
+// vt<int> _ZW1M2vtIiE instantiated in the importer: not preserved
+// S<int>::m _ZNW1M1SIiE1mE instantiated in the importer: not preserved
// hdrid _ZL5hdrid defined in ident.h: preserved by a module unit
// hdrptr _ZL6hdrptr that includes the header in its global module
-// fragment; diagnosed in a header unit
+// fragment; not preserved in a header unit
// RUN: split-file %s %t
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M5build,_ZL4priv,_ZW1M2iv \
// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -65,10 +66,6 @@
// RUN: -emit-llvm %t/m.pcm -o - | FileCheck %s --check-prefix=MOD \
// RUN: --implicit-check-not=@_ZW1M2iv
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZW1M2iv \
-// RUN: -fsyntax-only -verify=inline %t/m.cppm
-
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -emit-module-interface %t/m.cppm -o %t/m-noopt.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -77,17 +74,13 @@
// RUN: FileCheck %s --check-prefix=NOOPT < %t/m-noopt.ll
// RUN: FileCheck %s --check-prefix=NOOPTNOT < %t/m-noopt.ll
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -fmodule-file=M=%t/m.pcm -mloadtime-comment-vars=_ZW1M3ver \
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
+// RUN: -fmodule-file=M=%t/m.pcm \
+// RUN: -mloadtime-comment-vars=_ZW1M3ver,_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
// RUN: -emit-llvm %t/use.cpp -o %t/use.ll
// RUN: FileCheck %s --check-prefix=IMPORT < %t/use.ll
// RUN: FileCheck %s --check-prefix=IMPORTNOT < %t/use.ll
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -fmodule-file=M=%t/m.pcm \
-// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
-// RUN: -fsyntax-only -verify %t/use.cpp
-
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
// RUN: -emit-module-interface %t/gmf.cppm -o %t/gmf.pcm
@@ -97,10 +90,9 @@
// RUN: -fmodule-file=G=%t/gmf.pcm -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
// RUN: -emit-llvm %t/use-gmf.cpp -o - | FileCheck %s --check-prefix=GMFIMPORT
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
-// RUN: -emit-header-unit -xc++-user-header %t/ident.h -o %t/ident.pcm \
-// RUN: -verify=hu
+// RUN: -emit-header-unit -xc++-user-header %t/ident.h -o %t/ident.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -Wno-experimental-header-units -fmodule-file=%t/ident.pcm \
// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
@@ -115,23 +107,29 @@
// MOD-DAG: @llvm.compiler.used = appending global [3 x ptr]
// Without the option at BMI-build time nothing is preserved: the exported
-// variable is an ordinary global (the {{$}} anchor proves no metadata), and
-// the unreferenced internal and inline variables are not emitted at all.
-// NOOPT: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1{{$}}
+// variable and the non-exported module-linkage variable are defined as
+// ordinary globals (the {{$}} anchor proves no metadata), and the
+// unreferenced internal and inline variables are not emitted at all.
+// NOOPT-DAG: @_ZW1M3ver = global [16 x i8] c"@(#) module ver\00", align 1{{$}}
+// NOOPT-DAG: @_ZW1M5build = global [18 x i8] c"@(#) module build\00", align 1{{$}}
// NOOPTNOT-NOT: !loadtime_comment
// NOOPTNOT-NOT: @llvm.compiler.used
// NOOPTNOT-NOT: @_ZL4priv
// NOOPTNOT-NOT: @_ZW1M2iv
-// The importer instantiates the templates and re-emits the inline variable
-// it references (ordinary linkonce_odr definitions, no metadata — the {{$}}
-// anchor proves it), so nothing is preserved here. It emits neither the
-// module-owned non-inline variable it names nor the module-internal one.
+// The importer instantiates the variable template and the static data member
+// of the class template, both of which it names, and re-emits the inline
+// variable it references (ordinary linkonce_odr definitions, no metadata —
+// the {{$}} anchor proves it), so nothing is preserved here. The module-owned
+// non-inline variable it names and references is only declared, without
+// metadata; the other module-owned variable and the module-internal one are
+// not emitted.
+// IMPORT-DAG: @_ZW1M3ver = external global [16 x i8], align 1{{$}}
// IMPORT-DAG: @_ZW1M2vtIiE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
+// IMPORT-DAG: @_ZNW1M1SIiE1mE = linkonce_odr global ptr @{{.*}}, align 8{{$}}
// IMPORT-DAG: @_ZW1M2iv = linkonce_odr global ptr @{{.*}}, align 8{{$}}
// IMPORTNOT-NOT: !loadtime_comment
// IMPORTNOT-NOT: @llvm.compiler.used
-// IMPORTNOT-NOT: @_ZW1M3ver
// IMPORTNOT-NOT: @_ZW1M5build
// IMPORTNOT-NOT: @_ZL4priv
@@ -159,36 +157,33 @@ export module M;
export char ver[] = "@(#) module ver";
char build[] = "@(#) module build";
static char priv[] = "@(#) module priv";
-export inline const char *iv = "@(#) module inline"; // inline-warning {{'iv' named in '-mloadtime-comment-vars=' is an inline variable and will not be preserved}}
+export inline const char *iv = "@(#) module inline";
export template <class T> const char *vt = "@(#) vt";
export template <class T> struct S { static const char *m; };
template <class T> const char *S<T>::m = "@(#) sdm";
-export inline void touch() {}
//--- use.cpp
import M;
-const char *u1 = vt<int>; // expected-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
-const char *u2 = S<int>::m; // expected-note {{in instantiation of static data member 'S<int>::m' requested here}}
+const char *u0 = ver;
+const char *u1 = vt<int>;
+const char *u2 = S<int>::m;
const char *u3 = iv;
-// expected-warning at m.cppm:6 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
-// expected-warning at m.cppm:8 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
//--- ident.h
#ifndef IDENT_H
#define IDENT_H
-static char hdrid[] = "@(#) header id"; // hu-warning {{'hdrid' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
-static const char *hdrptr = "@(#) header ptr"; // hu-warning {{'hdrptr' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
+static char hdrid[] = "@(#) header id";
+static const char *hdrptr = "@(#) header ptr";
#endif
//--- gmf.cppm
module;
#include "ident.h"
export module G;
-export inline void touch_g() {}
//--- use-gmf.cpp
import G;
-void use_g() { touch_g(); }
+void use_g() {}
//--- use-hu.cpp
import "ident.h";
diff --git a/clang/test/Sema/loadtime-comment-vars-modules.cpp b/clang/test/Sema/loadtime-comment-vars-modules.cpp
new file mode 100644
index 0000000000000..94ca88daad7bd
--- /dev/null
+++ b/clang/test/Sema/loadtime-comment-vars-modules.cpp
@@ -0,0 +1,54 @@
+// Verify that -mloadtime-comment-vars= diagnoses the variables it cannot
+// preserve across C++20 module boundaries. The IR produced for these cases is
+// covered by clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp.
+//
+// Three scenarios are covered, each with its own -verify prefix:
+//
+// inline — a name-matched inline variable defined in a module unit is
+// diagnosed when the module unit is compiled.
+// import — specializations instantiated in an importing TU from the
+// imported template definitions are diagnosed in that TU, at the
+// pattern location in the module interface, with a note at the
+// instantiation point.
+// hu — name-matched variables defined in a header unit are diagnosed
+// when the header unit is built.
+
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZW1M2iv \
+// RUN: -fsyntax-only -verify=inline %t/m.cppm
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -fmodule-file=M=%t/m.pcm \
+// RUN: -mloadtime-comment-vars=_ZW1M2vtIiE,_ZNW1M1SIiE1mE \
+// RUN: -fsyntax-only -verify=import %t/use.cpp
+
+// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
+// RUN: -mloadtime-comment-vars=_ZL5hdrid,_ZL6hdrptr \
+// RUN: -emit-header-unit -xc++-user-header %t/ident.h -o %t/ident.pcm \
+// RUN: -verify=hu
+
+//--- m.cppm
+export module M;
+export inline const char *iv = "@(#) module inline"; // inline-warning {{'iv' named in '-mloadtime-comment-vars=' is an inline variable and will not be preserved}}
+export template <class T> const char *vt = "@(#) vt";
+export template <class T> struct S { static const char *m; };
+template <class T> const char *S<T>::m = "@(#) sdm";
+
+//--- use.cpp
+import M;
+const char *u1 = vt<int>; // import-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
+const char *u2 = S<int>::m; // import-note {{in instantiation of static data member 'S<int>::m' requested here}}
+// import-warning at m.cppm:3 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+// import-warning at m.cppm:5 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+
+//--- ident.h
+#ifndef IDENT_H
+#define IDENT_H
+static char hdrid[] = "@(#) header id"; // hu-warning {{'hdrid' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
+static const char *hdrptr = "@(#) header ptr"; // hu-warning {{'hdrptr' named in '-mloadtime-comment-vars=' is defined in a header unit and will not be preserved}}
+#endif
>From e40a233658c98c86acf5bd95746d366de2a96e3f Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Tue, 6 Oct 2026 09:50:24 +0530
Subject: [PATCH 29/31] [Clang][AIX] Accept a brace-enclosed string literal for
-mloadtime-comment-vars pointers
---
clang/lib/Sema/SemaDecl.cpp | 11 +++++++++-
.../CodeGen/PowerPC/loadtime-comment-vars.c | 21 ++++++++++++-------
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 16 +++++++++-----
clang/test/Sema/loadtime-comment-vars.c | 12 +++++++++--
4 files changed, 45 insertions(+), 15 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index eeebfc87526a2..bbaa09ffdf7d5 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15522,6 +15522,15 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
: AT ? AT->getElementType()
: QualType();
+ // The string literal that initializes a pointer may be enclosed in braces.
+ auto IsStringLiteralInit = [](const Expr *Init) {
+ Init = Init->IgnoreParenImpCasts();
+ if (const auto *ILE = dyn_cast<InitListExpr>(Init);
+ ILE && ILE->getNumInits() == 1)
+ Init = ILE->getInit(0)->IgnoreParenImpCasts();
+ return isa<StringLiteral>(Init);
+ };
+
Module *Mod = S.getCurrentModule();
std::optional<unsigned> Reason;
@@ -15571,7 +15580,7 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
// initialized variable only gets its value from a startup constructor, so
// the object would not contain the intended string.
Reason = diag::LoadTimeCommentVarReason::DynamicInit;
- else if (PT && !isa<StringLiteral>(VD->getInit()->IgnoreParenImpCasts()))
+ else if (PT && !IsStringLiteralInit(VD->getInit()))
// 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.
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index 06862bf390db4..6b74c7d63f378 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -16,18 +16,18 @@
// C, 32-bit and 64-bit AIX.
-// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c32.ll %s
-// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr -emit-llvm -disable-llvm-passes -o %t-c64.ll %s
-// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c32.ll
-// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar < %t-c64.ll
+// RUN: %clang_cc1 -O2 -triple powerpc-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr,braced -emit-llvm -disable-llvm-passes -o %t-c32.ll %s
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=sccsid,version,build_number,same_copyright,active,not_defined_here,tdefchar,ustr,sstr,braced -emit-llvm -disable-llvm-passes -o %t-c64.ll %s
+// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar -DBRACED=braced < %t-c32.ll
+// RUN: FileCheck %s -DSCCSID=sccsid -DVERSION=version -DSAME=same_copyright -DACTIVE=active -DTYPEDEFCHAR=tdefchar -DBRACED=braced < %t-c64.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-c32.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=copyright -DBUILDNUM=build_number -DBUILDDATA=build_data -DUSTR=ustr -DSSTR=sstr < %t-c64.ll
// The same source as C++: internal-linkage statics are matched by mangled
// name. (-w silences the C++ writable-strings compatibility warning for the
// legacy `static char *` idiom.)
-// RUN: %clang_cc1 -x c++ -w -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=_ZL6sccsid,_ZL7version,_ZL12build_number,_ZL14same_copyright,_ZL6active,not_defined_here,_ZL8tdefchar,_ZL4ustr,_ZL4sstr -emit-llvm -disable-llvm-passes -o %t-cxx.ll %s
-// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar < %t-cxx.ll
+// RUN: %clang_cc1 -x c++ -w -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=_ZL6sccsid,_ZL7version,_ZL12build_number,_ZL14same_copyright,_ZL6active,not_defined_here,_ZL8tdefchar,_ZL4ustr,_ZL4sstr,_ZL6braced -emit-llvm -disable-llvm-passes -o %t-cxx.ll %s
+// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar -DBRACED=_ZL6braced < %t-cxx.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=_ZL9copyright -DBUILDNUM=_ZL12build_number -DBUILDDATA=_ZL10build_data -DUSTR=_ZL4ustr -DSSTR=_ZL4sstr < %t-cxx.ll
// 1. A string pointer named in the list is preserved.
@@ -68,6 +68,10 @@ static CHAR *tdefchar = "@(#) typedef char";
static unsigned char ustr[] = "@(#) unsigned char string";
static signed char sstr[] = "@(#) signed char string";
+// 11. A pointer whose string-literal initializer is enclosed in braces is
+// preserved like the unbraced form.
+static const char *braced = {"@(#) braced"};
+
void foo() {}
// Listed, supported variables carry the metadata and stay alive.
@@ -80,13 +84,16 @@ void foo() {}
// CHECK-DAG: @[[SAME_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) same copyright\00", align {{[0-9]+}}
// CHECK-DAG: @[[TYPEDEFCHAR]] = internal global ptr @[[TYPEDEFCHAR_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[TYPEDEFCHAR_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) typedef char\00", align {{[0-9]+}}
+// CHECK-DAG: @[[BRACED]] = internal global ptr @[[BRACED_STR:.str(\.[0-9]+)?]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[BRACED_STR]] = private unnamed_addr constant [{{[0-9]+}} x i8] c"@(#) braced\00", align {{[0-9]+}}
-// CHECK: @llvm.compiler.used = appending global [5 x ptr]
+// CHECK: @llvm.compiler.used = appending global [6 x ptr]
// CHECK-SAME: ptr @[[SCCSID]]
// CHECK-SAME: ptr @[[VERSION]]
// CHECK-SAME: ptr @[[SAME]]
// CHECK-SAME: ptr @[[ACTIVE]]
// CHECK-SAME: ptr @[[TYPEDEFCHAR]]
+// CHECK-SAME: ptr @[[BRACED]]
// CHECK-SAME: section "llvm.metadata"
// The unlisted const string, the unsupported-type variables (including the
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index d2bf5329c46aa..1a0fe06465e43 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -38,6 +38,7 @@
// version asmid preserved (asm label)
// sccsid_ce _ZL9sccsid_ce preserved (static constexpr, internal)
// sccsid_ci sccsid_ci preserved (constinit; needs -std=c++20)
+// sccsid_br _ZL9sccsid_br preserved (braced string literal)
// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
// DecompositionDecl owns the storage)
//
@@ -54,7 +55,7 @@
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr \
-// RUN: -mloadtime-comment-vars=_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
+// RUN: -mloadtime-comment-vars=_ZL6u16str,_ZL5u8str,_ZL9sccsid_ce,sccsid_ci,_ZL9sccsid_br,_ZDC1a1b1cE,cver,_ZN12_GLOBAL__N_14anonE,asmid \
// RUN: -emit-llvm -disable-llvm-passes -o %t.ll %s
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
@@ -124,6 +125,7 @@ static char8_t u8str[] = u8"@(#) u8";
// what materializes the string in the object.
static constexpr const char *sccsid_ce = "@(#) constexpr";
constinit const char *sccsid_ci = "@(#) constinit";
+static const char *sccsid_br{"@(#) braced"};
// 10. Structured binding. The bindings a/b/c are BindingDecls with no storage
// of their own; the hidden DecompositionDecl (a VarDecl) owns the array,
@@ -210,22 +212,26 @@ char bar[] = "@(#) bar";
// CHECK-DAG: @_ZN12_GLOBAL__N_14anonE = internal global [10 x i8] c"@(#) anon\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @asmid = global [15 x i8] c"@(#) asm label\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// Eligible C++ forms: static constexpr (internal, constant) and constinit
-// (external) are both preserved.
+// Eligible C++ forms: static constexpr (internal, constant), constinit
+// (external), and a pointer list-initialized from a braced string literal
+// are all preserved.
// CHECK-DAG: @_ZL9sccsid_ce = internal constant ptr @[[CE_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[CE_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constexpr\00", align {{[0-9]+}}
// CHECK-DAG: @sccsid_ci = global ptr @[[CI_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
// CHECK-DAG: @[[CI_STR]] = private unnamed_addr constant [15 x i8] c"@(#) constinit\00", align {{[0-9]+}}
+// CHECK-DAG: @_ZL9sccsid_br = internal global ptr @[[BR_STR:.*]], align {{[0-9]+}}, !loadtime_comment ![[MD]]
+// CHECK-DAG: @[[BR_STR]] = private unnamed_addr constant [12 x i8] c"@(#) braced\00", align {{[0-9]+}}
// CHECK-DAG: @_ZDC1a1b1cE = internal constant [3 x i8] c"ab\00", align {{[0-9]+}}, !loadtime_comment ![[MD]]
-// The nine supported matched globals are preserved in llvm.compiler.used;
+// The ten supported matched globals are preserved in llvm.compiler.used;
// the two static data members are not.
-// CHECK: @llvm.compiler.used = appending global [9 x ptr]
+// CHECK: @llvm.compiler.used = appending global [10 x ptr]
// CHECK-SAME: @x
// CHECK-SAME: @_ZN1N1xE
// CHECK-SAME: @_ZN1NL3ptrE
// CHECK-SAME: @_ZL9sccsid_ce
// CHECK-SAME: @sccsid_ci
+// CHECK-SAME: @_ZL9sccsid_br
// CHECK-SAME: @_ZDC1a1b1cE
// CHECK-SAME: @cver
// CHECK-SAME: @_ZN12_GLOBAL__N_14anonE
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index 72d23574be324..d87594be05ab1 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -7,7 +7,9 @@
// - pointer-to-volatile character (volatile char *)
// - volatile character array (volatile char[])
// - thread-local variable (__thread, not static storage duration)
-// - pointer not initialized directly with a string literal
+// - pointer not initialized directly with a string literal, including
+// when the initializer is enclosed in braces; a braced string literal is
+// accepted without diagnostic
// - name-matched variables of an unsupported type (int, unsigned char)
// - variables defined as an alias (array and pointer) are diagnosed, while
// naming the aliased variable is accepted without diagnostic
@@ -19,7 +21,7 @@
// is rejected with an error.
// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wunused-const-variable \
-// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept,notchar,ustr,alias_arr,alias_ptr,aliasee_arr \
+// RUN: -mloadtime-comment-vars=vol_ptr,vol_char,vol_arr,tls_ptr,ind_ptr,const_arr,lfn,kept,notchar,ustr,alias_arr,alias_ptr,aliasee_arr,braced_ok,braced_ind \
// RUN: -fsyntax-only -verify %s
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu \
@@ -45,6 +47,12 @@ __thread char *tls_ptr = "@(#) tls"; // expected-warning {{'tls_ptr' named in '-
static const char target[] = "@(#) target";
const char *ind_ptr = target; // expected-warning {{'ind_ptr' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
+// Braces around the initializer do not change the outcome: a braced string
+// literal is accepted without diagnostic, and a braced pointer to another
+// object is diagnosed.
+const char *braced_ok = {"@(#) braced"};
+const char *braced_ind = {target}; // expected-warning {{'braced_ind' 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; no diagnostic is expected.
const char const_arr[] = "@(#) const arr";
>From efd56172cc2eb7f4c3a59d4b3d63ae6f509bcd25 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Tue, 6 Oct 2026 14:46:44 +0530
Subject: [PATCH 30/31] [Clang][AIX] Move the non-C++ -mloadtime-comment-vars
test scenarios into the C tests and address review comments
---
clang/lib/Sema/SemaDecl.cpp | 10 +-
.../CodeGen/PowerPC/loadtime-comment-vars.c | 47 +++++++-
.../CodeGen/PowerPC/loadtime-comment-vars.cpp | 105 +-----------------
.../Sema/loadtime-comment-vars-modules.cpp | 13 +--
clang/test/Sema/loadtime-comment-vars.c | 1 +
clang/test/Sema/loadtime-comment-vars.cpp | 39 ++++---
6 files changed, 80 insertions(+), 135 deletions(-)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index bbaa09ffdf7d5..f3bc3b061c479 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15549,10 +15549,12 @@ static void processForLoadTimeCommentVar(Sema &S, VarDecl *VD) {
else if (VD->isStaticDataMember())
Reason = diag::LoadTimeCommentVarReason::StaticDataMember;
else if (VD->isInline())
- // An inline variable is defined in every translation unit that sees it,
- // and a module importer re-emits it using the decision made when the
- // module was built, so the result would depend on option consistency
- // across compilations.
+ // An inline variable has no strong relationship to any particular
+ // translation unit. It is defined in every one that sees it, and which
+ // definition survives (and whether that one was compiled with the option)
+ // is not under the control of any single compilation. The weak binding is
+ // most noticeable with header units, where the definition is not compiled
+ // to an object file at all.
Reason = diag::LoadTimeCommentVarReason::Inline;
else if (VD->hasAttr<AliasAttr>())
// An alias has no storage of its own; the string belongs to the
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
index 6b74c7d63f378..96be3373350e0 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.c
@@ -1,5 +1,5 @@
-// Test Behavior of -mloadtime-comment-vars= for file-scope variables, in C and in
-// the same source compiled as C++:
+// Test the behavior of -mloadtime-comment-vars= for file-scope variables, in
+// C and in the same source compiled as C++ (CHECK and NOEMIT):
// * supported forms (plain-char pointer/array with a string-literal
// initializer) named in the list get !loadtime_comment metadata and are
// kept alive in llvm.compiler.used, even when otherwise unreferenced;
@@ -10,6 +10,12 @@
// * names are matched against the mangled IR name: in C that is the source
// identifier; in C++ a file-scope static mangles (sccsid -> _ZL6sccsid).
//
+// * STORAGE — storage-duration filtering: a thread-local variable and a
+// function-local static receive no metadata (their diagnostics are
+// covered by the Sema tests and silenced here);
+// * SPACE/DUP — list parsing: a name with a leading space matches nothing,
+// and a duplicate name preserves the variable exactly once.
+//
// Non-AIX behavior is covered elsewhere: the driver warns and drops the
// option (clang/test/Driver/mloadtime-comment-vars.c) and cc1 rejects it
// with an error (clang/test/Sema/loadtime-comment-vars.c).
@@ -30,6 +36,11 @@
// RUN: FileCheck %s -DSCCSID=_ZL6sccsid -DVERSION=_ZL7version -DSAME=_ZL14same_copyright -DACTIVE=_ZL6active -DTYPEDEFCHAR=_ZL8tdefchar -DBRACED=_ZL6braced < %t-cxx.ll
// RUN: FileCheck %s --check-prefix=NOEMIT -DCOPYRIGHT=_ZL9copyright -DBUILDNUM=_ZL12build_number -DBUILDDATA=_ZL10build_data -DUSTR=_ZL4ustr -DSSTR=_ZL4sstr < %t-cxx.ll
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var -mloadtime-comment-vars=keep,tl,stl,fn -emit-llvm -o - %s | FileCheck %s --check-prefix=STORAGE
+
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix "-mloadtime-comment-vars=one, two" -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=SPACE
+// RUN: %clang_cc1 -O2 -triple powerpc64-ibm-aix -mloadtime-comment-vars=one,one -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=DUP
+
// 1. A string pointer named in the list is preserved.
static char *sccsid = "@(#) sccsid Version 1.0";
@@ -74,6 +85,18 @@ static const char *braced = {"@(#) braced"};
void foo() {}
+// Sources for the STORAGE scenario. keep has static storage duration and is
+// preserved; the thread-local variables and the function-local static (whose
+// IR name is g.fn) are name-matched but receive no metadata.
+const char *keep = "@(#) keep";
+__thread const char *tl = "@(#) tl";
+static __thread const char *stl = "@(#) stl";
+void g(void) { static const char *fn = "@(#) fn"; (void)fn; }
+
+// Sources for the SPACE and DUP scenarios.
+char one[] = "@(#) one";
+char two[] = "@(#) two";
+
// Listed, supported variables carry the metadata and stay alive.
// CHECK-DAG: @[[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]+}}
@@ -106,3 +129,23 @@ void foo() {}
// NOEMIT-NOT: @[[USTR]]
// NOEMIT-NOT: @[[SSTR]]
// NOEMIT-NOT: @not_defined_here
+
+// Storage-duration filtering: only the file-scope static-duration variable
+// has metadata and appears in llvm.compiler.used.
+// STORAGE: @keep = {{.*}}!loadtime_comment
+// STORAGE-NOT: @tl = {{.*}}!loadtime_comment
+// STORAGE-NOT: @stl = {{.*}}!loadtime_comment
+// STORAGE-NOT: @g.fn = {{.*}}!loadtime_comment
+// STORAGE: @llvm.compiler.used = appending global [1 x ptr] [ptr @keep], section "llvm.metadata"
+
+// List parsing, "one, two": the leading space means ' two' matches nothing;
+// only one is preserved (the {{$}} anchor proves two has no metadata).
+// SPACE-DAG: @one = global [9 x i8] c"@(#) one\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// SPACE-DAG: @two = global [9 x i8] c"@(#) two\00", align {{[0-9]+}}{{$}}
+// SPACE-DAG: @llvm.compiler.used = appending global [1 x ptr] [ptr @one], section "llvm.metadata"
+
+// List parsing, "one,one": a duplicate name preserves the variable exactly
+// once.
+// DUP-DAG: @one = global [9 x i8] c"@(#) one\00", align {{[0-9]+}}, !loadtime_comment !{{[0-9]+}}
+// DUP-DAG: @two = global [9 x i8] c"@(#) two\00", align {{[0-9]+}}{{$}}
+// DUP-DAG: @llvm.compiler.used = appending global [1 x ptr] [ptr @one], section "llvm.metadata"
diff --git a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
index 1a0fe06465e43..c3945d78fea70 100644
--- a/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
+++ b/clang/test/CodeGen/PowerPC/loadtime-comment-vars.cpp
@@ -1,5 +1,6 @@
-// Test -mloadtime-comment-vars= IR output for C++ on AIX. Three scenarios
-// are covered, each with its own set of named variables and check prefix:
+// Test -mloadtime-comment-vars= IR output for C++-specific forms on AIX.
+// Behaviour that is not specific to C++ (storage-duration filtering, list
+// parsing, the option given more than once) is covered by the C tests.
//
// CHECK — mangled-name matching: file- and namespace-scope variables
// receive !loadtime_comment metadata and are added to
@@ -12,14 +13,7 @@
// by the Sema tests and silenced here. The names are split
// across two occurrences of the option, which are combined.
//
-// STORAGE — storage-duration filtering: thread_local variables are
-// diagnosed by Sema and receive no metadata; a function-local
-// static is diagnosed and receives no metadata.
-//
-// SPACE/DUP — list-parsing edge cases: a name with a leading space matches
-// nothing; a duplicate name preserves the variable exactly once.
-//
-// Names used in the matching scenario:
+// Names used:
//
// Source IR symbol Expected treatment
// ------ --------- ------------------
@@ -41,17 +35,6 @@
// sccsid_br _ZL9sccsid_br preserved (braced string literal)
// [a, b, c] _ZDC1a1b1cE preserved (structured binding: the
// DecompositionDecl owns the storage)
-//
-// Names used in the storage scenario (namespaces and structs are renamed to
-// avoid redefinition against the matching-scenario symbols):
-//
-// Source IR symbol Expected treatment
-// ------ --------- ------------------
-// keep keep preserved (file scope, static duration)
-// S::tl _ZN1S2tlE thread_local: diagnosed, no metadata
-// stl _ZL3stl static thread_local: diagnosed, no metadata
-// T::tm _ZN1T2tmE thread_local static data member: diagnosed
-// g()::fn _ZZ1gvE2fn function-local static: diagnosed, no metadata
// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix -Wno-loadtime-comment-var \
// RUN: -mloadtime-comment-vars=x,_ZN1N1xE,_ZN1NL3ptrE,_ZN1A1xE,_ZN1B3verE,_ZN1C4infoE,not_string,_ZL4wstr \
@@ -60,20 +43,8 @@
// RUN: FileCheck %s < %t.ll
// RUN: FileCheck %s --check-prefix=NOEMIT < %t.ll
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=keep,_ZN1S2tlE,_ZL3stl,_ZN1T2tmE,_ZZ1gvE2fn \
-// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefix=STORAGE
-
-// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
-// RUN: "-mloadtime-comment-vars=foo, bar" \
-// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=SPACE
-
-// RUN: %clang_cc1 -std=c++20 -O2 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=foo,foo \
-// RUN: -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=DUP
-
// ===========================================================================
-// Mangled-name matching
+// Sources
// ===========================================================================
// 1. A file-scope array is not mangled in C++ (the IR name equals the
@@ -149,43 +120,7 @@ char anon[] = "@(#) anon";
char version[] asm("asmid") = "@(#) asm label";
// ===========================================================================
-// Storage-duration filtering (STORAGE)
-// ===========================================================================
-
-// 14. A file-scope pointer with static storage duration is preserved.
-const char *keep = "@(#) keep";
-
-namespace S {
-// 15. A thread_local variable (N renamed to S to avoid redefinition) is
-// diagnosed and receives no metadata.
-thread_local const char *tl = "@(#) tl";
-} // namespace S
-
-// 16. The 'static' specifier changes linkage only; the storage duration is
-// still thread, so this is diagnosed as well.
-static thread_local const char *stl = "@(#) stl";
-
-// 17. A thread_local static data member (A renamed to T) is diagnosed and
-// receives no metadata.
-struct T {
- static thread_local const char *tm;
-};
-thread_local const char *T::tm = "@(#) tm";
-
-// 18. Function-local static (f renamed to g) — name-matched, so diagnosed by
-// Sema (see the Sema tests); receives no metadata either way.
-void g() { static const char *fn = "@(#) fn"; (void)fn; }
-
-// ===========================================================================
-// Sources — list-parsing edge cases (SPACE, DUP)
-// ===========================================================================
-
-// 19. Simple arrays used only by the SPACE/DUP checks.
-char foo[] = "@(#) foo";
-char bar[] = "@(#) bar";
-
-// ===========================================================================
-// CHECK patterns — mangled-name matching
+// CHECK patterns
// ===========================================================================
// File-scope x and namespace N::x both matched.
@@ -246,31 +181,3 @@ char bar[] = "@(#) bar";
// NOEMIT-NOT: @_ZL4wstr
// NOEMIT-NOT: @_ZL6u16str
// NOEMIT-NOT: @_ZL5u8str
-
-// ===========================================================================
-// STORAGE patterns — thread_local variables get no metadata
-// ===========================================================================
-
-// Only the file-scope static-duration variable is preserved: it has metadata
-// and appears in llvm.compiler.used. The thread_local variables are diagnosed
-// by Sema and receive neither.
-// STORAGE: @keep = {{.*}}!loadtime_comment
-// STORAGE-NOT: @_ZN1S2tlE = {{.*}}!loadtime_comment
-// STORAGE-NOT: @_ZL3stl = {{.*}}!loadtime_comment
-// STORAGE-NOT: @_ZN1T2tmE = {{.*}}!loadtime_comment
-// STORAGE-NOT: @_ZZ1gvE2fn = {{.*}}!loadtime_comment
-// STORAGE: @llvm.compiler.used = appending global [1 x ptr] [ptr @keep], section "llvm.metadata"
-
-// ===========================================================================
-// SPACE/DUP patterns — list-parsing edge cases
-// ===========================================================================
-
-// "foo, bar": leading space means ' bar' matches nothing; only foo is preserved.
-// 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"
-
-// "foo,foo": duplicate name preserves the variable exactly once.
-// 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"
diff --git a/clang/test/Sema/loadtime-comment-vars-modules.cpp b/clang/test/Sema/loadtime-comment-vars-modules.cpp
index 94ca88daad7bd..20a56473cff87 100644
--- a/clang/test/Sema/loadtime-comment-vars-modules.cpp
+++ b/clang/test/Sema/loadtime-comment-vars-modules.cpp
@@ -2,10 +2,8 @@
// preserve across C++20 module boundaries. The IR produced for these cases is
// covered by clang/test/CodeGen/PowerPC/loadtime-comment-vars-modules.cpp.
//
-// Three scenarios are covered, each with its own -verify prefix:
+// Two scenarios are covered, each with its own -verify prefix:
//
-// inline — a name-matched inline variable defined in a module unit is
-// diagnosed when the module unit is compiled.
// import — specializations instantiated in an importing TU from the
// imported template definitions are diagnosed in that TU, at the
// pattern location in the module interface, with a note at the
@@ -16,10 +14,6 @@
// RUN: rm -rf %t
// RUN: split-file %s %t
-// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZW1M2iv \
-// RUN: -fsyntax-only -verify=inline %t/m.cppm
-
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
// RUN: -emit-module-interface %t/m.cppm -o %t/m.pcm
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -34,7 +28,6 @@
//--- m.cppm
export module M;
-export inline const char *iv = "@(#) module inline"; // inline-warning {{'iv' named in '-mloadtime-comment-vars=' is an inline variable and will not be preserved}}
export template <class T> const char *vt = "@(#) vt";
export template <class T> struct S { static const char *m; };
template <class T> const char *S<T>::m = "@(#) sdm";
@@ -43,8 +36,8 @@ template <class T> const char *S<T>::m = "@(#) sdm";
import M;
const char *u1 = vt<int>; // import-note {{in instantiation of variable template specialization 'vt<int>' requested here}}
const char *u2 = S<int>::m; // import-note {{in instantiation of static data member 'S<int>::m' requested here}}
-// import-warning at m.cppm:3 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
-// import-warning at m.cppm:5 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
+// import-warning at m.cppm:2 {{'vt<int>' named in '-mloadtime-comment-vars=' is a variable template specialization and will not be preserved}}
+// import-warning at m.cppm:4 {{'m' named in '-mloadtime-comment-vars=' is a static data member and will not be preserved}}
//--- ident.h
#ifndef IDENT_H
diff --git a/clang/test/Sema/loadtime-comment-vars.c b/clang/test/Sema/loadtime-comment-vars.c
index d87594be05ab1..aa9477878ca9e 100644
--- a/clang/test/Sema/loadtime-comment-vars.c
+++ b/clang/test/Sema/loadtime-comment-vars.c
@@ -11,6 +11,7 @@
// when the initializer is enclosed in braces; a braced string literal is
// accepted without diagnostic
// - name-matched variables of an unsupported type (int, unsigned char)
+// - function-local static (name-matched, so diagnosed rather than ignored)
// - variables defined as an alias (array and pointer) are diagnosed, while
// naming the aliased variable is accepted without diagnostic
// - valid const char array — no diagnostic
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
index 81604c7a30eaf..daf48ef9fb925 100644
--- a/clang/test/Sema/loadtime-comment-vars.cpp
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -4,23 +4,22 @@
// Three scenarios are covered, each with its own set of named variables and
// its own -verify prefix:
//
-// storage — storage duration: thread_local variables and a name-matched
-// function-local static are diagnosed; a namespace-scope
-// variable is accepted without diagnostic. Also covers a
-// name-matched variable of an unsupported (wide character)
-// type, which is diagnosed.
+// storage — storage duration: thread_local variables, including a
+// thread_local static data member, are diagnosed; a
+// namespace-scope variable is accepted without diagnostic.
// init — initializer form: dynamically initialized pointers, and
// constant initializers that are not a direct string literal
// (pointer to another global, consteval call, user-defined
// literal), are diagnosed. (-std=c++20 for consteval.)
-// kinds — unsupported declaration kinds: name-matched static data
-// members (out-of-line, in-class inline, instantiated from a
-// class template), variable template specializations
-// (explicit and implicit), and namespace-scope inline variables
-// are diagnosed.
+// kinds — unsupported declaration kinds and types: a name-matched
+// function-local static, static data members (out-of-line,
+// in-class inline, instantiated from a class template), variable
+// template specializations (explicit and implicit),
+// namespace-scope inline variables, and a variable of an
+// unsupported (wide character) type are diagnosed.
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE,_ZZ1fvE2fn,_ZL4wstr \
+// RUN: -mloadtime-comment-vars=keep,_ZN1N2tlE,_ZL3stl,_ZN1A2tmE \
// RUN: -fsyntax-only -verify=storage %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -28,7 +27,7 @@
// RUN: -fsyntax-only -verify=init %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=_ZN2B23sidE,_ZN2B34isidE,_Z2vtIcE,_Z2vtIiE,_ZN2SCIiE1mE,_ZN1M2ivE \
+// RUN: -mloadtime-comment-vars=_ZZ1fvE2fn,_ZN2B23sidE,_ZN2B34isidE,_Z2vtIcE,_Z2vtIiE,_ZN2SCIiE1mE,_ZN1M2ivE,_ZL4wstr \
// RUN: -fsyntax-only -verify=kinds %s
// ---- storage: storage-duration cases ----------------------------------------
@@ -52,13 +51,6 @@ struct A {
// The storage-duration reason outranks the static-data-member one.
thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '-mloadtime-comment-vars=' does not have static storage duration and will not be preserved}}
-// Function-local static: a name match demonstrates intent, so it is
-// diagnosed rather than silently ignored.
-void f() { static const char *fn = "@(#) fn"; (void)fn; } // storage-warning {{'fn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
-
-// A name match on a variable of an unsupported type is diagnosed as well.
-static wchar_t wstr[] = L"@(#) w"; // storage-warning {{'wstr' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
-
// ---- init: initializer-form cases --------------------------------------------
const char *make();
@@ -94,7 +86,14 @@ typedef __SIZE_TYPE__ size_t;
constexpr const char *operator""_id(const char *str, size_t) { return str; }
const char *p_udl = "@(#) udl"_id; // init-warning {{'p_udl' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
-// ---- kinds: unsupported declaration kinds ------------------------------------
+// ---- kinds: unsupported declaration kinds and types --------------------------
+
+// Function-local static: a name match demonstrates intent, so it is
+// diagnosed rather than silently ignored.
+void f() { static const char *fn = "@(#) fn"; (void)fn; } // kinds-warning {{'fn' named in '-mloadtime-comment-vars=' is a function-local variable and will not be preserved}}
+
+// A name match on a variable of an unsupported type is diagnosed as well.
+static wchar_t wstr[] = L"@(#) w"; // kinds-warning {{'wstr' named in '-mloadtime-comment-vars=' does not have a plain char pointer or array type and will not be preserved}}
// An out-of-line static data member definition is diagnosed.
struct B2 {
>From 23c05b5c0f180e8fc9044c0503a98d2acd25e2c9 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Tue, 6 Oct 2026 14:53:14 +0530
Subject: [PATCH 31/31] [Clang][AIX] Test a dynamically initialized array for
-mloadtime-comment-vars
---
clang/test/Sema/loadtime-comment-vars.cpp | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/clang/test/Sema/loadtime-comment-vars.cpp b/clang/test/Sema/loadtime-comment-vars.cpp
index daf48ef9fb925..1388674ff4970 100644
--- a/clang/test/Sema/loadtime-comment-vars.cpp
+++ b/clang/test/Sema/loadtime-comment-vars.cpp
@@ -7,8 +7,8 @@
// storage — storage duration: thread_local variables, including a
// thread_local static data member, are diagnosed; a
// namespace-scope variable is accepted without diagnostic.
-// init — initializer form: dynamically initialized pointers, and
-// constant initializers that are not a direct string literal
+// init — initializer form: dynamically initialized pointers and
+// arrays, and constant initializers that are not a direct string literal
// (pointer to another global, consteval call, user-defined
// literal), are diagnosed. (-std=c++20 for consteval.)
// kinds — unsupported declaration kinds and types: a name-matched
@@ -23,7 +23,7 @@
// RUN: -fsyntax-only -verify=storage %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
-// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,p_ind,p_ce,p_udl \
+// RUN: -mloadtime-comment-vars=p_ok,arr_ok,p_dyn,arr_dyn,p_ind,p_ce,p_udl \
// RUN: -fsyntax-only -verify=init %s
// RUN: %clang_cc1 -std=c++20 -triple powerpc64-ibm-aix \
@@ -54,6 +54,7 @@ thread_local const char *A::tm = "@(#) tm"; // storage-warning {{'tm' named in '
// ---- init: initializer-form cases --------------------------------------------
const char *make();
+char make_char();
// A pointer bound directly to a string literal is supported; no diagnostic
// is expected.
@@ -70,6 +71,10 @@ const char src[] = "@(#) src";
// would not be present in the object file at load time.
const char *p_dyn = make(); // init-warning {{'p_dyn' named in '-mloadtime-comment-vars=' is not constant-initialized and will not be preserved}}
+// An array whose elements come from a non-constant call is dynamically
+// initialized as well, and is diagnosed for the same reason.
+char arr_dyn[] = {make_char(), '\0'}; // init-warning {{'arr_dyn' named in '-mloadtime-comment-vars=' is not constant-initialized and will not be preserved}}
+
// This pointer is constant-initialized but bound to another global rather
// than a string literal, so it is diagnosed.
const char *p_ind = src; // init-warning {{'p_ind' named in '-mloadtime-comment-vars=' is not initialized with a string literal and will not be preserved}}
More information about the cfe-commits
mailing list