[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
Wed Jul 29 18:54:32 PDT 2026


https://github.com/tonykuttai updated https://github.com/llvm/llvm-project/pull/187986

>From 4686b46e2499bef1407059e3bb745cb8273c32f9 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 19 Jun 2026 15:46:49 +0530
Subject: [PATCH 1/9] [Clang][AIX] Add -mloadtime-comment-vars support to
 preserve variables in the final object file.

---
 clang/docs/LanguageExtensions.md              |  65 ++++++++++
 clang/include/clang/Basic/CodeGenOptions.h    |   3 +
 clang/include/clang/Options/Options.td        |   7 ++
 clang/lib/CodeGen/CodeGenModule.cpp           | 119 ++++++++++++++++++
 clang/lib/CodeGen/CodeGenModule.h             |  18 +++
 clang/lib/Driver/ToolChains/Clang.cpp         |   9 ++
 .../CodeGen/PowerPC/loadtime-comment-mixed.c  |  12 ++
 clang/test/CodeGen/loadtime-comment-vars.c    |  61 +++++++++
 clang/test/Driver/mloadtime-comment-vars.c    |  11 ++
 .../lower-comment-string.ll                   |  21 ++--
 10 files changed, 318 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/CodeGen/PowerPC/loadtime-comment-mixed.c
 create mode 100644 clang/test/CodeGen/loadtime-comment-vars.c
 create mode 100644 clang/test/Driver/mloadtime-comment-vars.c

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index f5313e0378ca0..410647270f2f5 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6510,6 +6510,71 @@ When `#pragma comment(copyright, ...)` appears in a C++20 module interface
 unit, the copyright string is embedded only in the object file compiled from
 that interface unit. Importing TUs do not re-emit the string.
 
+### Preserving Identifying Variables with -mloadtime-comment-vars
+
+The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
+global variable names that should be preserved in the final object file as
+loadtime identifying strings. This is an AIX-specific feature and is ignored
+on other targets.
+
+This flag complements `#pragma comment(copyright, ...)` for codebases that
+already use the traditional UNIX convention of embedding identifying strings
+directly in source variables rather than via a pragma.
+
+Syntax:
+
+```console
+-mloadtime-comment-vars=<var1>[,<var2>,...]
+```
+
+Valid variable types:
+
+A variable named in the list must meet both of these conditions to be
+preserved:
+
+- Its type must be a character pointer (`char *`, `const char *`) or a
+  character array (`char[]`).
+- It must have an initializer.
+
+Variables that fail either check -- for example, an `int` or a `struct` --
+are silently skipped. Variables that appear in the list but are not defined in
+the translation unit are also ignored.
+
+Example:
+
+```c
+static char *sccsid = "@(#) MyApp Version 1.0";
+static char  version[] = "@(#) Built 2026-05-24";
+
+void foo() {}
+```
+
+Compiled with:
+
+```console
+clang -target powerpc64-ibm-aix \
+  -mloadtime-comment-vars=sccsid,version \
+  -c source.c -o source.o
+```
+
+Both `sccsid` and `version` survive optimization and are retained in the
+object file.
+
+```console
+$ what source.o
+source.o:
+         MyApp Version 1.0
+         Built 2026-05-24
+```
+
+Interaction with `#pragma comment(copyright, ...)`:
+
+The two mechanisms can be used together in the same translation unit. The
+pragma produces a dedicated `__loadtime_comment_str` symbol placed in the
+`__loadtime_comment` section, while `-mloadtime-comment-vars` preserves
+the named source variables in place using `.ref` directives. Both sets of
+strings appear in the final object file independently.
+
 ## Evaluating Object Size
 
 Clang supports the builtins `__builtin_object_size` and
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index c12434135a198..768882d346c9b 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -355,6 +355,9 @@ class CodeGenOptions : public CodeGenOptionsBase {
   /// A list of linker options to embed in the object file.
   std::vector<std::string> LinkerOptions;
 
+  /// List of global variable names to preserve as loadtime comment variables.
+  std::vector<std::string> LoadTimeCommentVars;
+
   /// Name of the profile file to use as output for -fprofile-instr-generate,
   /// -fprofile-generate, and -fcs-profile-generate.
   std::string InstrProfileOutput;
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 07fec9e9aac21..7a6b7bec74dc3 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4879,6 +4879,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 5f5fc4401bb4e..8cc82861dd37e 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1132,6 +1132,13 @@ void CodeGenModule::Release() {
   Module *Primary = getContext().getCurrentNamedModule();
   if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
     EmitModuleInitializers(Primary);
+
+  // Queue loadtime comment variable candidates into the deferred emission
+  // list before EmitDeferred() runs, so their initializers (which may
+  // reference other globals, e.g. static const char *p = a;) are emitted
+  // through the normal infrastructure with correct ordering.
+  QueueLoadTimeCommentVarEmission();
+
   EmitDeferred();
   DeferredDecls.insert_range(EmittedDeferredDecls);
   EmittedDeferredDecls.clear();
@@ -1828,6 +1835,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());
 
@@ -4388,6 +4398,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 54b08b588dfde..2a150eda428ea 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2211,6 +2211,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 fb24d0b877ca6..f8b9b31069c14 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6360,6 +6360,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 88a17f54122658df97eedd62d77fe15170810eef Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Mon, 22 Jun 2026 14:30:46 +0530
Subject: [PATCH 2/9] [Clang][AIX] Handle -mloadtime-comment-vars in global var
 emission

---
 clang/docs/LanguageExtensions.md              |  17 +-
 clang/lib/CodeGen/CodeGenModule.cpp           | 150 +++++++-----------
 clang/lib/CodeGen/CodeGenModule.h             |  15 +-
 .../CodeGen/loadtime-comment-vars-cxx.cpp     |  85 ++++++++++
 clang/test/CodeGen/loadtime-comment-vars.c    |  10 +-
 clang/test/Driver/mloadtime-comment-vars.c    |   4 +
 6 files changed, 176 insertions(+), 105 deletions(-)
 create mode 100644 clang/test/CodeGen/loadtime-comment-vars-cxx.cpp

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 410647270f2f5..9db01587a98b4 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6514,8 +6514,9 @@ that interface unit. Importing TUs do not re-emit the string.
 
 The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
 global variable names that should be preserved in the final object file as
-loadtime identifying strings. This is an AIX-specific feature and is ignored
-on other targets.
+loadtime identifying strings. This is an AIX-specific feature; on other
+targets the compiler emits a warning and the flag is not forwarded to
+`-cc1`.
 
 This flag complements `#pragma comment(copyright, ...)` for codebases that
 already use the traditional UNIX convention of embedding identifying strings
@@ -6527,6 +6528,18 @@ Syntax:
 -mloadtime-comment-vars=<var1>[,<var2>,...]
 ```
 
+Name matching:
+
+- In C, names are matched as plain identifiers (for example, `sccsid`).
+- In C++, names containing `::` are treated as source-qualified names and
+  matched against the declaration's qualified source name (for example,
+  `N::x` or `A::x`).
+- In C++, names without `::` are treated as unqualified names and matched by
+  plain identifier. This may match more than one declaration when names are
+  reused across scopes.
+- To target a single declaration in C++, prefer qualified names. Unqualified
+  matches can preserve additional globals and increase object size.
+
 Valid variable types:
 
 A variable named in the list must meet both of these conditions to be
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 8cc82861dd37e..ea0f072305f5d 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1133,12 +1133,6 @@ void CodeGenModule::Release() {
   if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
     EmitModuleInitializers(Primary);
 
-  // Queue loadtime comment variable candidates into the deferred emission
-  // list before EmitDeferred() runs, so their initializers (which may
-  // reference other globals, e.g. static const char *p = a;) are emitted
-  // through the normal infrastructure with correct ordering.
-  QueueLoadTimeCommentVarEmission();
-
   EmitDeferred();
   DeferredDecls.insert_range(EmittedDeferredDecls);
   EmittedDeferredDecls.clear();
@@ -1835,9 +1829,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());
 
@@ -4336,7 +4327,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);
@@ -4398,23 +4394,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;
@@ -4423,88 +4415,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) {
@@ -6646,6 +6607,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 2a150eda428ea..9cec859b89882 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2219,16 +2219,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 53bd1e0c6cbf0fc9c5864f05c4884ee1ee9bd3dd Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Thu, 25 Jun 2026 12:56:50 +0530
Subject: [PATCH 3/9] [Clang][AIX] Switch -mloadtime-comment-vars name matching
 to mangled IR names

Replace source-qualified name matching in matchesLoadTimeCommentVarName with
mangled IR symbol name matching via getMangledName(GlobalDecl(VD)).
---
 clang/docs/LanguageExtensions.md              | 33 ++++++++----
 clang/lib/CodeGen/CodeGenModule.cpp           | 53 +++++--------------
 clang/lib/CodeGen/CodeGenModule.h             | 11 ++--
 .../CodeGen/loadtime-comment-vars-cxx.cpp     | 52 ++++++++++--------
 clang/test/Driver/mloadtime-comment-vars.c    |  6 +--
 5 files changed, 74 insertions(+), 81 deletions(-)

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 9db01587a98b4..eec28dd75cc74 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6530,15 +6530,30 @@ Syntax:
 
 Name matching:
 
-- In C, names are matched as plain identifiers (for example, `sccsid`).
-- In C++, names containing `::` are treated as source-qualified names and
-  matched against the declaration's qualified source name (for example,
-  `N::x` or `A::x`).
-- In C++, names without `::` are treated as unqualified names and matched by
-  plain identifier. This may match more than one declaration when names are
-  reused across scopes.
-- To target a single declaration in C++, prefer qualified names. Unqualified
-  matches can preserve additional globals and increase object size.
+Names are matched against the variable's **mangled IR symbol name** — the
+name as it appears in the object file.
+
+- In C, file-scope static variables are not mangled, so the mangled name is
+  identical to the source identifier (for example, `sccsid`).
+- In C++, variables are mangled using the Itanium ABI. To find the mangled
+  name, compile with `clang -S -emit-llvm` and look for the global in the
+  `.ll` output, or run `nm` on the object file.
+
+```console
+# Find the mangled name of a C++ variable
+$ clang++ -S -emit-llvm -o - source.cpp | grep '@.*sccsid'
+ at _ZN1N6sccsidE = ...
+
+# Or use nm on the object file
+$ nm source.o | grep sccsid
+0000000000000000 b _ZN1N6sccsidE
+
+# Then pass the mangled name to the flag
+-mloadtime-comment-vars=_ZN1N6sccsidE
+```
+
+Mangled names are unique, so each entry in the list selects exactly one
+variable. Unrecognised names are silently ignored.
 
 Valid variable types:
 
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index ea0f072305f5d..850acce11ac86 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4415,54 +4415,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 9cec859b89882..252e6cb70056f 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2217,15 +2217,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 cb4c2084f62ad62b331cf2a0645be5ab072a63a2 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tonypalampalliyil at gmail.com>
Date: Thu, 25 Jun 2026 21:03:23 +0530
Subject: [PATCH 4/9] Apply suggestions from code review

Co-authored-by: Hubert Tong <hubert.reinterpretcast at gmail.com>
---
 clang/docs/LanguageExtensions.md | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index eec28dd75cc74..bee91bdf24f7c 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6515,8 +6515,7 @@ that interface unit. Importing TUs do not re-emit the string.
 The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
 global variable names that should be preserved in the final object file as
 loadtime identifying strings. This is an AIX-specific feature; on other
-targets the compiler emits a warning and the flag is not forwarded to
-`-cc1`.
+targets the compiler emits a warning.
 
 This flag complements `#pragma comment(copyright, ...)` for codebases that
 already use the traditional UNIX convention of embedding identifying strings
@@ -6530,14 +6529,10 @@ Syntax:
 
 Name matching:
 
-Names are matched against the variable's **mangled IR symbol name** — the
-name as it appears in the object file.
+Names are matched against the variable's mangled name.
 
 - In C, file-scope static variables are not mangled, so the mangled name is
   identical to the source identifier (for example, `sccsid`).
-- In C++, variables are mangled using the Itanium ABI. To find the mangled
-  name, compile with `clang -S -emit-llvm` and look for the global in the
-  `.ll` output, or run `nm` on the object file.
 
 ```console
 # Find the mangled name of a C++ variable
@@ -6553,7 +6548,6 @@ $ nm source.o | grep sccsid
 ```
 
 Mangled names are unique, so each entry in the list selects exactly one
-variable. Unrecognised names are silently ignored.
 
 Valid variable types:
 

>From 73c98a2e1e1807eb65ba527b570efcc7886a305a Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 26 Jun 2026 15:54:01 +0530
Subject: [PATCH 5/9] [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 bee91bdf24f7c..51e6d7c9e3430 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6513,9 +6513,10 @@ that interface unit. Importing TUs do not re-emit the string.
 ### Preserving Identifying Variables with -mloadtime-comment-vars
 
 The `-mloadtime-comment-vars=` flag accepts a comma-separated list of
-global variable names that should be preserved in the final object file as
+mangled variable names that should be preserved in the final object file as
 loadtime identifying strings. This is an AIX-specific feature; on other
-targets the compiler emits a warning.
+targets the compiler emits a warning. Names are matched against each
+variable's mangled name, and unrecognised names are silently ignored.
 
 This flag complements `#pragma comment(copyright, ...)` for codebases that
 already use the traditional UNIX convention of embedding identifying strings
@@ -6527,40 +6528,45 @@ Syntax:
 -mloadtime-comment-vars=<var1>[,<var2>,...]
 ```
 
-Name matching:
+In C, variable names are not mangled, so the mangled name is identical to the source
+identifier (for example, `sccsid`). In C++, the mangled name follows the
+Itanium C++ ABI, so a namespace-scoped or class-scoped variable must be named
+using its mangled form:
 
-Names are matched against the variable's mangled name.
-
-- In C, file-scope static variables are not mangled, so the mangled name is
-  identical to the source identifier (for example, `sccsid`).
+```c++
+namespace N { char sccsid[] = "@(#) MyApp Version 1.0"; }   // N::sccsid   -> _ZN1N6sccsidE
+const char *App::version = "@(#) Built 2026-06-25";         // App::version -> _ZN3App7versionE
+```
 
 ```console
-# Find the mangled name of a C++ variable
-$ clang++ -S -emit-llvm -o - source.cpp | grep '@.*sccsid'
- at _ZN1N6sccsidE = ...
-
-# Or use nm on the object file
-$ nm source.o | grep sccsid
-0000000000000000 b _ZN1N6sccsidE
-
-# Then pass the mangled name to the flag
--mloadtime-comment-vars=_ZN1N6sccsidE
+-mloadtime-comment-vars=_ZN1N6sccsidE,_ZN3App7versionE
 ```
 
-Mangled names are unique, so each entry in the list selects exactly one
-
 Valid variable types:
 
-A variable named in the list must meet both of these conditions to be
+A variable named in the list must meet all of these conditions to be
 preserved:
 
+- It must be defined at file, namespace, or class scope (a function-local
+  `static` variable is not supported).
 - Its type must be a character pointer (`char *`, `const char *`) or a
-  character array (`char[]`).
-- It must have an initializer.
-
-Variables that fail either check -- for example, an `int` or a `struct` --
-are silently skipped. Variables that appear in the list but are not defined in
-the translation unit are also ignored.
+  character array (`char[]`, `const char[]`).
+- It must have static storage duration and must not be `volatile`-qualified.
+- It must be constant-initialized, so that the string is present in the object
+  at load time. A dynamically initialized variable (whose value is computed by
+  a start-up constructor) is not preserved.
+- A character *pointer* must be initialized directly with a string literal (for
+  example, `char *p = "@(#) ...";`). A pointer bound to some other object
+  -- even a constant one, such as another character array -- does not itself
+  carry the identifying string and is not preserved.
+
+A variable that is named in the list but is `volatile`-qualified, does not
+have static storage duration (for example, a `thread_local` variable), is
+dynamically initialized, or is a pointer not bound to a string literal, is
+diagnosed with a warning and is not preserved. Variables of an unsupported type
+-- for example, an `int` or a `struct` -- or without an initializer are
+silently skipped, as are function-local `static` variables and names that are
+not defined in the translation unit.
 
 Example:
 
@@ -6579,8 +6585,7 @@ clang -target powerpc64-ibm-aix \
   -c source.c -o source.o
 ```
 
-Both `sccsid` and `version` survive optimization and are retained in the
-object file.
+Both `sccsid` and `version` are retained in the object file.
 
 ```console
 $ what source.o
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 768882d346c9b..2f29a9807c4b2 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -355,7 +355,7 @@ class CodeGenOptions : public CodeGenOptionsBase {
   /// A list of linker options to embed in the object file.
   std::vector<std::string> LinkerOptions;
 
-  /// List of global variable names to preserve as loadtime comment variables.
+  /// List of mangled variable names to preserve as loadtime comment variables.
   std::vector<std::string> LoadTimeCommentVars;
 
   /// Name of the profile file to use as output for -fprofile-instr-generate,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index a688b298f2bbd..78da7f2dfae77 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 b7072634cccf3..3c5160e999599 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1639,6 +1639,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 7a6b7bec74dc3..dc346e766cb4a 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4883,8 +4883,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 850acce11ac86..fbd79aef28f46 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4328,11 +4328,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);
@@ -4394,25 +4390,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
@@ -4430,15 +4452,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) {
@@ -6583,12 +6642,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 252e6cb70056f..a8bd851b10e44 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2212,19 +2212,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 d02bce1e0596171b0b342b736bfaad9e4019678f Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 17 Jul 2026 19:43:29 +0530
Subject: [PATCH 6/9] 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 4785a9772933299d4b306a1f8c57e7a802839ef8 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Fri, 17 Jul 2026 19:52:18 +0530
Subject: [PATCH 7/9] 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 fbd79aef28f46..2e80579591528 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -1132,7 +1132,6 @@ void CodeGenModule::Release() {
   Module *Primary = getContext().getCurrentNamedModule();
   if (CXX20ModuleInits && Primary && !Primary->isHeaderLikeModule())
     EmitModuleInitializers(Primary);
-
   EmitDeferred();
   DeferredDecls.insert_range(EmittedDeferredDecls);
   EmittedDeferredDecls.clear();

>From 85a676732733440ada434602cac3b1da6ecf8617 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 8/9] [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 28fea9a99b609..4d9e4422bf9fd 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -389,6 +389,13 @@ latest release, please see the [Clang Web Site](https://clang.llvm.org) or the
 
 #### 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 1a5bd2301dfc8..fbf10998dad37 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4930,6 +4930,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 2f29a9807c4b2..c12434135a198 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -355,9 +355,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 78da7f2dfae77..a688b298f2bbd 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 89e2f956971b3..080ed9d25bf72 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3853,6 +3853,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 f21131622d03d..5a1bda8ec8464 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -546,6 +546,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 dc346e766cb4a..3fdac1ba1025b 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -4885,7 +4885,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 2e80579591528..69a56a7455a34 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -4327,7 +4327,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);
@@ -4389,116 +4389,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);
 
@@ -6638,11 +6528,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 a8bd851b10e44..54b08b588dfde 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -2211,46 +2211,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 f8b9b31069c14..4cdebe092ba67 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6360,7 +6360,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 7de5542e72559..2da8245068101 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"
@@ -15338,6 +15339,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);
@@ -15452,6 +15520,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 e77eb2aea6fecba4bfa354fc9699ca04542f913b 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 9/9] [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 51e6d7c9e3430..f4e3b1b5fc7bf 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -6530,16 +6530,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:
@@ -6547,26 +6548,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 080ed9d25bf72..da671aaa218e7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3857,7 +3857,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 5a1bda8ec8464..ccc44e23b93d4 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -704,6 +704,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 8a30f6319bcef..874d725690b8a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4189,6 +4189,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 a2c069c575e61..20e8f8573d840 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 2da8245068101..b7e901d41f0eb 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15343,47 +15343,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
@@ -15406,6 +15433,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);
@@ -15523,9 +15554,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 921a9f965fb9c..d5d6f18872218 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -6555,6 +6555,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}



More information about the cfe-commits mailing list