[clang] [llvm] [PowerPC][AIX] Support #pragma comment copyright for AIX (PR #178184)
Tony Varghese via cfe-commits
cfe-commits at lists.llvm.org
Tue Jan 27 04:03:02 PST 2026
https://github.com/tonykuttai created https://github.com/llvm/llvm-project/pull/178184
- Emit `!aix.copyright.comment` from Clang for the pragma.
- Lower it in LLVM to a TU-local string + llvm.used + `!implicit.ref`.
- Add module-import and backend relocation tests.
This change builds on top of [XCOFF associated metadata](https://github.com/llvm/llvm-project/pull/159096) and was reviewed at [\[PowerPC\]\[AIX\] Support #pragma comment copyright for AIX ](https://github.com/mandlebug/llvm-project/pull/1)
>From 750cee4021ac3c8b8c791a4e2ee6b57582d7e667 Mon Sep 17 00:00:00 2001
From: Tony Varghese <tony.varghese at ibm.com>
Date: Sat, 18 Oct 2025 11:39:43 -0400
Subject: [PATCH] [PowerPC][AIX] Support #pragma comment copyright for AIX
- Emit !aix.copyright.comment from Clang for the pragma.
- Lower it in LLVM to a TU-local string + llvm.used + !implicit.ref.
- Add module-import and backend relocation tests.
---
.../clang/Basic/DiagnosticParseKinds.td | 3 +
clang/include/clang/Basic/PragmaKinds.h | 3 +-
clang/lib/AST/TextNodeDumper.cpp | 3 +
clang/lib/CodeGen/CodeGenModule.cpp | 39 +++++
clang/lib/CodeGen/CodeGenModule.h | 5 +
clang/lib/Parse/ParsePragma.cpp | 45 ++++--
.../pragma-comment-copyright-aix-modules.cpp | 28 ++++
.../PowerPC/pragma-comment-copyright-aix.c | 34 ++++
clang/test/CodeGen/lto-newpm-pipeline.c | 2 +
.../Transforms/Utils/LowerCommentStringPass.h | 25 +++
llvm/lib/Passes/PassBuilder.cpp | 1 +
llvm/lib/Passes/PassBuilderPipelines.cpp | 7 +
llvm/lib/Passes/PassRegistry.def | 1 +
llvm/lib/Transforms/Utils/CMakeLists.txt | 1 +
.../Utils/LowerCommentStringPass.cpp | 148 ++++++++++++++++++
.../CodeGen/AArch64/print-pipeline-passes.ll | 3 +-
llvm/test/Other/new-pm-defaults.ll | 1 +
.../Other/new-pm-thinlto-postlink-defaults.ll | 1 +
.../new-pm-thinlto-postlink-pgo-defaults.ll | 1 +
...-pm-thinlto-postlink-samplepgo-defaults.ll | 1 +
.../lower-comment-string.ll | 59 +++++++
21 files changed, 399 insertions(+), 12 deletions(-)
create mode 100644 clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix-modules.cpp
create mode 100644 clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix.c
create mode 100644 llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
create mode 100644 llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
create mode 100644 llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index e5e071f43fa75..2700b8a65d267 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -1365,6 +1365,9 @@ def err_pragma_comment_unknown_kind : Error<"unknown kind of pragma comment">;
// PS4 recognizes only #pragma comment(lib)
def warn_pragma_comment_ignored : Warning<"'#pragma comment %0' ignored">,
InGroup<IgnoredPragmas>;
+def warn_pragma_comment_once : Warning<"'#pragma comment %0' "
+ "can be specified only once per translation unit - ignored">,
+ InGroup<IgnoredPragmas>;
// - #pragma detect_mismatch
def err_pragma_detect_mismatch_malformed : Error<
"pragma detect_mismatch is malformed; it requires two comma-separated "
diff --git a/clang/include/clang/Basic/PragmaKinds.h b/clang/include/clang/Basic/PragmaKinds.h
index 42f049f7323d2..52ca58855d460 100644
--- a/clang/include/clang/Basic/PragmaKinds.h
+++ b/clang/include/clang/Basic/PragmaKinds.h
@@ -17,7 +17,8 @@ enum PragmaMSCommentKind {
PCK_Lib, // #pragma comment(lib, ...)
PCK_Compiler, // #pragma comment(compiler, ...)
PCK_ExeStr, // #pragma comment(exestr, ...)
- PCK_User // #pragma comment(user, ...)
+ PCK_User, // #pragma comment(user, ...)
+ PCK_Copyright // #pragma comment(copyright, ...)
};
enum PragmaMSStructKind {
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 41aebdb8d2f1b..7af6a63594612 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2523,6 +2523,9 @@ void TextNodeDumper::VisitPragmaCommentDecl(const PragmaCommentDecl *D) {
case PCK_User:
OS << "user";
break;
+ case PCK_Copyright:
+ OS << "copyright";
+ break;
}
StringRef Arg = D->getArg();
if (!Arg.empty())
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index c5eb14e329315..fb387cbe00914 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -40,7 +40,9 @@
#include "clang/Basic/Builtins.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/Module.h"
+#include "clang/Basic/PragmaKinds.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/Version.h"
@@ -1560,6 +1562,12 @@ void CodeGenModule::Release() {
EmitBackendOptionsMetadata(getCodeGenOpts());
+ // Emit copyright metadata
+ if (LoadTimeComment) {
+ auto *NMD = getModule().getOrInsertNamedMetadata("comment_string.loadtime");
+ NMD->addOperand(LoadTimeComment);
+ }
+
// If there is device offloading code embed it in the host now.
EmbedObject(&getModule(), CodeGenOpts, *getFileSystem(), getDiags());
@@ -3322,6 +3330,31 @@ void CodeGenModule::AddDependentLib(StringRef Lib) {
LinkerOptionsMetadata.push_back(llvm::MDNode::get(C, MDOpts));
}
+/// Process AIX copyright pragma and create LLVM metadata.
+/// #pragma comment(copyright, "string") embed copyright
+/// information into the object file's loader section.
+///
+/// Example: #pragma comment(copyright, "Copyright IBM Corp. 2024")
+///
+/// This should only be called once per translation unit.
+void CodeGenModule::ProcessPragmaComment(PragmaMSCommentKind Kind,
+ StringRef Comment) {
+ // Ensure we are only processing Copyright Pragmas
+ assert(Kind == PCK_Copyright &&
+ "Unexpected pragma comment kind, ProcessPragmaComment should only be "
+ "called for PCK_Copyright");
+
+ // Only one copyright pragma allowed per translation unit
+ if (LoadTimeComment) {
+ return;
+ }
+
+ // Create llvm metadata with the comment string
+ auto &C = getLLVMContext();
+ llvm::Metadata *Ops[] = {llvm::MDString::get(C, Comment)};
+ LoadTimeComment = llvm::MDNode::get(C, Ops);
+}
+
/// Add link options implied by the given module, including modules
/// it depends on, using a postorder walk.
static void addLinkOptionsPostorder(CodeGenModule &CGM, Module *Mod,
@@ -7458,6 +7491,12 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
case PCK_Lib:
AddDependentLib(PCD->getArg());
break;
+ case PCK_Copyright:
+ // Skip pragmas deserialized from modules/PCHs. Process the pragma comment
+ // only if it originated in this TU and the target OS is AIX.
+ if (!PCD->isFromASTFile() && getTriple().isOSAIX())
+ ProcessPragmaComment(PCD->getCommentKind(), PCD->getArg());
+ break;
case PCK_Compiler:
case PCK_ExeStr:
case PCK_User:
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 3971b296b3f80..89af24ebd90f5 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -587,6 +587,9 @@ class CodeGenModule : public CodeGenTypeCache {
/// A vector of metadata strings for dependent libraries for ELF.
SmallVector<llvm::MDNode *, 16> ELFDependentLibraries;
+ /// Metadata for copyright pragma comment (if present).
+ llvm::MDNode *LoadTimeComment = nullptr;
+
/// @name Cache for Objective-C runtime types
/// @{
@@ -1458,6 +1461,8 @@ class CodeGenModule : public CodeGenTypeCache {
/// Appends a dependent lib to the appropriate metadata value.
void AddDependentLib(StringRef Lib);
+ /// Process pragma comment
+ void ProcessPragmaComment(PragmaMSCommentKind Kind, StringRef Comment);
llvm::GlobalVariable::LinkageTypes getFunctionLinkage(GlobalDecl GD);
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 98933811265e8..ead9f9eabe0c6 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -236,6 +236,7 @@ struct PragmaCommentHandler : public PragmaHandler {
private:
Sema &Actions;
+ bool SeenCopyrightInTU = false; // TU-scoped
};
struct PragmaDetectMismatchHandler : public PragmaHandler {
@@ -473,7 +474,8 @@ void Parser::initializePragmaHandlers() {
PP.AddPragmaHandler(OpenACCHandler.get());
if (getLangOpts().MicrosoftExt ||
- getTargetInfo().getTriple().isOSBinFormatELF()) {
+ getTargetInfo().getTriple().isOSBinFormatELF() ||
+ getTargetInfo().getTriple().isOSAIX()) {
MSCommentHandler = std::make_unique<PragmaCommentHandler>(Actions);
PP.AddPragmaHandler(MSCommentHandler.get());
}
@@ -595,7 +597,8 @@ void Parser::resetPragmaHandlers() {
OpenACCHandler.reset();
if (getLangOpts().MicrosoftExt ||
- getTargetInfo().getTriple().isOSBinFormatELF()) {
+ getTargetInfo().getTriple().isOSBinFormatELF() ||
+ getTargetInfo().getTriple().isOSAIX()) {
PP.RemovePragmaHandler(MSCommentHandler.get());
MSCommentHandler.reset();
}
@@ -3201,13 +3204,21 @@ void PragmaCommentHandler::HandlePragma(Preprocessor &PP,
// Verify that this is one of the 5 explicitly listed options.
IdentifierInfo *II = Tok.getIdentifierInfo();
PragmaMSCommentKind Kind =
- llvm::StringSwitch<PragmaMSCommentKind>(II->getName())
- .Case("linker", PCK_Linker)
- .Case("lib", PCK_Lib)
- .Case("compiler", PCK_Compiler)
- .Case("exestr", PCK_ExeStr)
- .Case("user", PCK_User)
- .Default(PCK_Unknown);
+ llvm::StringSwitch<PragmaMSCommentKind>(II->getName())
+ .Case("linker", PCK_Linker)
+ .Case("lib", PCK_Lib)
+ .Case("compiler", PCK_Compiler)
+ .Case("exestr", PCK_ExeStr)
+ .Case("user", PCK_User)
+ .Case("copyright", PCK_Copyright)
+ .Default(PCK_Unknown);
+
+ // Restrict copyright to AIX targets only. This could be applied for z/OS
+ // and extended with other IBM pragma comment kinds.
+ if (!PP.getTargetInfo().getTriple().isOSAIX() && Kind == PCK_Copyright) {
+ Kind = PCK_Unknown;
+ }
+
if (Kind == PCK_Unknown) {
PP.Diag(Tok.getLocation(), diag::err_pragma_comment_unknown_kind);
return;
@@ -3219,6 +3230,18 @@ void PragmaCommentHandler::HandlePragma(Preprocessor &PP,
return;
}
+ // On AIX, pragma comment copyright can each appear only once in a TU.
+ if (Kind == PCK_Copyright) {
+ assert(PP.getTargetInfo().getTriple().isOSAIX() &&
+ "Pragma Comment Copyright is supported only on AIX");
+ if (SeenCopyrightInTU) {
+ PP.Diag(Tok.getLocation(), diag::warn_pragma_comment_once)
+ << II->getName();
+ return;
+ }
+ SeenCopyrightInTU = true;
+ }
+
// Read the optional string if present.
PP.Lex(Tok);
std::string ArgumentString;
@@ -3245,6 +3268,10 @@ void PragmaCommentHandler::HandlePragma(Preprocessor &PP,
return;
}
+ // Accept and ignore well-formed copyright with empty string.
+ if (Kind == PCK_Copyright && ArgumentString.empty())
+ return;
+
// If the pragma is lexically sound, notify any interested PPCallbacks.
if (PP.getPPCallbacks())
PP.getPPCallbacks()->PragmaComment(CommentLoc, II, ArgumentString);
diff --git a/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix-modules.cpp b/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix-modules.cpp
new file mode 100644
index 0000000000000..9020df6e737d6
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix-modules.cpp
@@ -0,0 +1,28 @@
+// RUN: split-file %s %t
+
+// Build the module interface to a PCM
+// RUN: %clang_cc1 -std=c++20 -triple powerpc-ibm-aix \
+// RUN: -emit-module-interface %t/copymod.cppm -o %t/copymod.pcm
+
+// Verify that module interface emits copyright global when compiled to IR
+// RUN: %clang_cc1 -std=c++20 -triple powerpc-ibm-aix -emit-llvm %t/copymod.cppm -o - \
+// RUN: | FileCheck %s --check-prefix=CHECK-MOD
+// CHECK-MOD: @__loadtime_comment_str = internal unnamed_addr constant [10 x i8] c"module me\00", section "__loadtime_comment"
+// CHECK-MOD: @llvm.used = appending global {{.*}} @__loadtime_comment_str
+
+// Compile an importing TU that uses the prebuilt module and verify that it
+// does NOT re-emit the module's copyright global.
+// RUN: %clang_cc1 -std=c++20 -triple powerpc-ibm-aix \
+// RUN: -fprebuilt-module-path=%t -emit-llvm %t/importmod.cc -o - \
+// RUN: | FileCheck %s --check-prefix=CHECK-IMPORT
+// CHECK-IMPORT-NOT: @__loadtime_comment_str
+// CHECK-IMPORT-NOT: c"module me\00"
+
+//--- copymod.cppm
+export module copymod;
+#pragma comment(copyright, "module me")
+export inline void f() {}
+
+//--- importmod.cc
+import copymod;
+void g() { f(); }
diff --git a/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix.c b/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix.c
new file mode 100644
index 0000000000000..98d2db416496e
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/pragma-comment-copyright-aix.c
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 %s -triple powerpc-ibm-aix -O0 -disable-llvm-passes -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple powerpc64-ibm-aix -O0 -disable-llvm-passes -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple powerpc-ibm-aix -verify
+// RUN: %clang_cc1 %s -triple powerpc64-ibm-aix -verify
+// RUN: %clang_cc1 %s -DTEST_EMPTY_COPYRIGHT -triple powerpc-ibm-aix -verify
+
+// RUN: %clang_cc1 %s -x c++ -triple powerpc-ibm-aix -O0 -disable-llvm-passes -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -x c++ -triple powerpc64-ibm-aix -O0 -disable-llvm-passes -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -x c++ -triple powerpc-ibm-aix -verify
+// RUN: %clang_cc1 %s -x c++ -triple powerpc64-ibm-aix -verify
+// RUN: %clang_cc1 %s -x c++ -DTEST_EMPTY_COPYRIGHT -triple powerpc-ibm-aix -verify
+
+#ifndef TEST_EMPTY_COPYRIGHT
+// Test basic pragma comment types
+#pragma comment(copyright, "@(#) Copyright")
+
+// Test duplicate copyright - should warn and ignore
+#pragma comment(copyright, "Duplicate Copyright") // expected-warning {{'#pragma comment copyright' can be specified only once per translation unit - ignored}}
+
+int main() { return 0; }
+
+// Check that both metadata sections are present
+// CHECK: !comment_string.loadtime = !{![[copyright:[0-9]+]]}
+
+// Check individual metadata content
+// CHECK: ![[copyright]] = !{!"@(#) Copyright"}
+
+#else
+// Test empty copyright string - valid with no warning
+#pragma comment(copyright, "") // expected-no-diagnostics
+
+int main() { return 0; }
+
+#endif
diff --git a/clang/test/CodeGen/lto-newpm-pipeline.c b/clang/test/CodeGen/lto-newpm-pipeline.c
index ea9784a76f923..b9466b9558b06 100644
--- a/clang/test/CodeGen/lto-newpm-pipeline.c
+++ b/clang/test/CodeGen/lto-newpm-pipeline.c
@@ -27,6 +27,7 @@
// CHECK-FULL-O0: Running pass: VerifierPass
// CHECK-FULL-O0-NEXT: Running analysis: VerifierAnalysis
+// CHECK-FULL-O0-NEXT: Running pass: LowerCommentStringPass
// CHECK-FULL-O0-NEXT: Running analysis: InnerAnalysisManagerProxy
// CHECK-FULL-O0-NEXT: Running pass: EntryExitInstrumenterPass
// CHECK-FULL-O0-NEXT: Running pass: AlwaysInlinerPass
@@ -41,6 +42,7 @@
// CHECK-THIN-O0: Running pass: VerifierPass
// CHECK-THIN-O0-NEXT: Running analysis: VerifierAnalysis
+// CHECK-THIN-O0-NEXT: Running pass: LowerCommentStringPass
// CHECK-THIN-O0-NEXT: Running analysis: InnerAnalysisManagerProxy
// CHECK-THIN-O0-NEXT: Running pass: EntryExitInstrumenterPass
// CHECK-THIN-O0-NEXT: Running pass: AlwaysInlinerPass
diff --git a/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h b/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
new file mode 100644
index 0000000000000..567c42ddd3714
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/LowerCommentStringPass.h
@@ -0,0 +1,25 @@
+//===-- LowerCommentStringPass.h - Lower Comment string metadata -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_LOWERCOMMENTSTRINGPASS_H
+#define LLVM_TRANSFORMS_UTILS_LOWERCOMMENTSTRINGPASS_H
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+class LowerCommentStringPass : public PassInfoMixin<LowerCommentStringPass> {
+public:
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+
+ static bool isRequired() { return true; }
+};
+
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_LOWERCOMMENTSTRINGPASS_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 53cf0046bd858..a4fb575909d31 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -347,6 +347,7 @@
#include "llvm/Transforms/Utils/BreakCriticalEdges.h"
#include "llvm/Transforms/Utils/CanonicalizeAliases.h"
#include "llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h"
+#include "llvm/Transforms/Utils/LowerCommentStringPass.h"
#include "llvm/Transforms/Utils/CountVisits.h"
#include "llvm/Transforms/Utils/DXILUpgrade.h"
#include "llvm/Transforms/Utils/Debugify.h"
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 3f3939eaf0eb0..910c8a6c10103 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -134,6 +134,7 @@
#include "llvm/Transforms/Utils/AddDiscriminators.h"
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
#include "llvm/Transforms/Utils/CanonicalizeAliases.h"
+#include "llvm/Transforms/Utils/LowerCommentStringPass.h"
#include "llvm/Transforms/Utils/CountVisits.h"
#include "llvm/Transforms/Utils/EntryExitInstrumenter.h"
#include "llvm/Transforms/Utils/ExtraPassManager.h"
@@ -1451,6 +1452,9 @@ PassBuilder::buildModuleOptimizationPipeline(OptimizationLevel Level,
const bool LTOPreLink = isLTOPreLink(LTOPhase);
ModulePassManager MPM;
+ // Process copyright metadata early, before any optimizations
+ MPM.addPass(LowerCommentStringPass());
+
// Run partial inlining pass to partially inline functions that have
// large bodies.
if (RunPartialInlining)
@@ -2249,6 +2253,9 @@ PassBuilder::buildO0DefaultPipeline(OptimizationLevel Level,
ModulePassManager MPM;
+ // Process copyright metadata at O0 before any other transformations
+ MPM.addPass(LowerCommentStringPass());
+
// Perform pseudo probe instrumentation in O0 mode. This is for the
// consistency between different build modes. For example, a LTO build can be
// mixed with an O0 prelink and an O2 postlink. Loading a sample profile in
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 884d8daf63463..67c6ea2a1f96e 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -60,6 +60,7 @@ MODULE_PASS("check-debugify", NewPMCheckDebugifyPass())
MODULE_PASS("constmerge", ConstantMergePass())
MODULE_PASS("coro-cleanup", CoroCleanupPass())
MODULE_PASS("coro-early", CoroEarlyPass())
+MODULE_PASS("lower-comment-string", LowerCommentStringPass())
MODULE_PASS("cross-dso-cfi", CrossDSOCFIPass())
MODULE_PASS("ctx-instr-gen",
PGOInstrumentationGen(PGOInstrumentationType::CTXPROF))
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index f367ca2fdf56b..943add58cca5d 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -17,6 +17,7 @@ add_llvm_component_library(LLVMTransformUtils
CodeLayout.cpp
CodeMoverUtils.cpp
ControlFlowUtils.cpp
+ LowerCommentStringPass.cpp
CtorUtils.cpp
CountVisits.cpp
Debugify.cpp
diff --git a/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp b/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
new file mode 100644
index 0000000000000..5173a29adcaf3
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/LowerCommentStringPass.cpp
@@ -0,0 +1,148 @@
+//===-- LowerCommentStringPass.cpp - Lower Comment string metadata -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===---------------------------------------------------------------------===//
+//
+// LowerCommentStringPass pass lowers module-level comment string metadata
+// emitted by Clang:
+//
+// !comment_string.loadtime = !{!"Copyright ..."}
+//
+// into concrete, translation-unit–local globals.
+// This Pass is enabled only for AIX.
+// For each module (translation unit), the pass performs the following:
+//
+// 1. Creates a null-terminated, internal constant string global
+// (`__loadtime_comment_str`) containing the copyright text in
+// `__loadtime_comment` section.
+//
+// 2. Marks the string in `llvm.used` so it cannot be dropped by
+// optimization or LTO.
+//
+// 3. Attaches `!implicit.ref` metadata referencing the string to every
+// defined function in the module. The PowerPC AIX backend recognizes
+// this metadata and emits a `.ref` directive from the function to the
+// string, creating a concrete relocation that prevents the linker from
+// discarding it (as long as the referencing symbol is kept).
+//
+// Input IR:
+// !comment_string.loadtime = !{!"Copyright"}
+// Output IR:
+// @__loadtime_comment_str = internal constant [N x i8] c"Copyright\00",
+// section "__loadtime_comment"
+// @llvm.used = appending global [1 x ptr] [ptr @__loadtime_comment_str]
+//
+// define i32 @func() !implicit.ref !5 { ... }
+// !5 = !{ptr @__loadtime_comment_str}
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/LowerCommentStringPass.h"
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/GlobalValue.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/MDBuilder.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Type.h"
+#include "llvm/IR/Value.h"
+#include "llvm/Passes/PassBuilder.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/TargetParser/Triple.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
+
+#define DEBUG_TYPE "lower-comment-string"
+
+using namespace llvm;
+
+static cl::opt<bool>
+ DisableCopyrightMetadata("disable-lower-comment-string", cl::ReallyHidden,
+ cl::desc("Disable LowerCommentString pass."),
+ cl::init(false));
+
+static bool isAIXTriple(const Module &M) {
+ return Triple(M.getTargetTriple()).isOSAIX();
+}
+
+PreservedAnalyses LowerCommentStringPass::run(Module &M,
+ ModuleAnalysisManager &AM) {
+ if (DisableCopyrightMetadata || !isAIXTriple(M))
+ return PreservedAnalyses::all();
+
+ LLVMContext &Ctx = M.getContext();
+
+ // Single-metadata: !comment_string.loadtime = !{!0}
+ // Each operand node is expected to have one MDString operand.
+ NamedMDNode *MD = M.getNamedMetadata("comment_string.loadtime");
+ if (!MD || MD->getNumOperands() == 0)
+ return PreservedAnalyses::all();
+
+ // At this point we are guarateed that one TU contains a single copyright
+ // metadata entry. Create TU-local string global for that metadata entry.
+ MDNode *MdNode = MD->getOperand(0);
+ if (!MdNode || MdNode->getNumOperands() == 0)
+ return PreservedAnalyses::all();
+
+ auto *MdString = dyn_cast_or_null<MDString>(MdNode->getOperand(0));
+ if (!MdString)
+ return PreservedAnalyses::all();
+
+ StringRef Text = MdString->getString();
+ if (Text.empty())
+ return PreservedAnalyses::all();
+
+ // 1. Create a single NULL-terminated string global
+ Constant *StrInit = ConstantDataArray::getString(Ctx, Text, /*AddNull=*/true);
+
+ // Internal, constant, TU-local--avoids duplicate symbol issues across TUs.
+ auto *StrGV = new GlobalVariable(M, StrInit->getType(),
+ /*isConstant=*/true,
+ GlobalValue::InternalLinkage, StrInit,
+ /*Name=*/"__loadtime_comment_str");
+ // Set unnamed_addr to allow the linker to merge identical strings
+ StrGV->setUnnamedAddr(GlobalValue::UnnamedAddr::Global);
+ StrGV->setAlignment(Align(1));
+ // Place in the "__loadtime_comment" section.
+ // The GV is constant, so we expect a read-only section.
+ StrGV->setSection("__loadtime_comment");
+
+ // 2. Add the string to llvm.used to prevent LLVM optimization/LTO passes from
+ // removing it.
+ appendToUsed(M, {StrGV});
+
+ // 3. Attach !implicit ref to every defined function
+ // Create a metadata node pointing to the copyright string:
+ // !N = !{ptr @__loadtime_comment_str}
+ Metadata *Ops[] = {ConstantAsMetadata::get(StrGV)};
+ MDNode *ImplicitRefMD = MDNode::get(Ctx, Ops);
+
+ // Lambda to attach implicit.ref metadata to a function.
+ auto AddImplicitRef = [&](Function &F) {
+ if (F.isDeclaration())
+ return;
+ // Attach the implicit.ref metadata to the function
+ F.setMetadata("implicit.ref", ImplicitRefMD);
+ LLVM_DEBUG(dbgs() << "[copyright] attached implicit.ref to function: "
+ << F.getName() << "\n");
+ };
+
+ // Process all functions in the module
+ for (Function &F : M)
+ AddImplicitRef(F);
+
+ // Cleanup the processed metadata.
+ MD->eraseFromParent();
+ LLVM_DEBUG(dbgs() << "[copyright] created string and anchor for module\n");
+
+ return PreservedAnalyses::all();
+}
diff --git a/llvm/test/CodeGen/AArch64/print-pipeline-passes.ll b/llvm/test/CodeGen/AArch64/print-pipeline-passes.ll
index 5852f97a63798..eaa4441029fff 100644
--- a/llvm/test/CodeGen/AArch64/print-pipeline-passes.ll
+++ b/llvm/test/CodeGen/AArch64/print-pipeline-passes.ll
@@ -2,8 +2,7 @@
; RUN: opt -mtriple=aarch64 -S -passes='default<O2>' -print-pipeline-passes < %s | FileCheck %s
; CHECK: loop-idiom-vectorize
-; O0: {{^}}function(ee-instrument<>),always-inline,coro-cond(coro-early,cgscc(coro-split),coro-cleanup,globaldce),function(annotation-remarks),verify,print{{$}}
-
+; O0: {{^}}lower-comment-string,function(ee-instrument<>),always-inline,coro-cond(coro-early,cgscc(coro-split),coro-cleanup,globaldce),alloc-token,function(annotation-remarks),verify,print{{$}}
define void @foo() {
entry:
ret void
diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll
index 94e860b8ce304..c66e730ed9767 100644
--- a/llvm/test/Other/new-pm-defaults.ll
+++ b/llvm/test/Other/new-pm-defaults.ll
@@ -239,6 +239,7 @@
; CHECK-O-NEXT: Running pass: CoroCleanupPass
; CHECK-O-NEXT: Running pass: GlobalOptPass
; CHECK-O-NEXT: Running pass: GlobalDCEPass
+; CHECK-O-NEXT: Running pass: LowerCommentStringPass
; CHECK-DEFAULT-NEXT: Running pass: EliminateAvailableExternallyPass
; CHECK-LTO-NOT: Running pass: EliminateAvailableExternallyPass
; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
index a08a140a35166..5274345bb012a 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll
@@ -163,6 +163,7 @@
; CHECK-O-NEXT: Running pass: CoroCleanupPass
; CHECK-POSTLINK-O-NEXT: Running pass: GlobalOptPass
; CHECK-POSTLINK-O-NEXT: Running pass: GlobalDCEPass
+; CHECK-POSTLINK-O-NEXT: Running pass: LowerCommentStringPass
; CHECK-POSTLINK-O-NEXT: Running pass: EliminateAvailableExternallyPass
; CHECK-POSTLINK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
; CHECK-POSTLINK-O-NEXT: Running pass: RecomputeGlobalsAAPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
index d9e2dd37a7985..3333a434bf9df 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll
@@ -147,6 +147,7 @@
; CHECK-O-NEXT: Running pass: CoroCleanupPass
; CHECK-O-NEXT: Running pass: GlobalOptPass
; CHECK-O-NEXT: Running pass: GlobalDCEPass
+; CHECK-O-NEXT: Running pass: LowerCommentStringPass
; CHECK-O-NEXT: Running pass: EliminateAvailableExternallyPass
; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
; CHECK-O-NEXT: Running pass: RecomputeGlobalsAAPass
diff --git a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
index 2f6fa4b27d354..794c6c767f010 100644
--- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
+++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll
@@ -156,6 +156,7 @@
; CHECK-O-NEXT: Running pass: CoroCleanupPass
; CHECK-O-NEXT: Running pass: GlobalOptPass
; CHECK-O-NEXT: Running pass: GlobalDCEPass
+; CHECK-O-NEXT: Running pass: LowerCommentStringPass
; CHECK-O-NEXT: Running pass: EliminateAvailableExternallyPass
; CHECK-O-NEXT: Running pass: ReversePostOrderFunctionAttrsPass
; CHECK-O-NEXT: Running pass: RecomputeGlobalsAAPass
diff --git a/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
new file mode 100644
index 0000000000000..ba7ad17f4c4b7
--- /dev/null
+++ b/llvm/test/Transforms/LowerCommentString/lower-comment-string.ll
@@ -0,0 +1,59 @@
+; RUN: opt -passes=lower-comment-string -S %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-O0
+
+; 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
+
+; Verify that LowerCommentStringPass lowers !loadtime.copyright.comment
+; into concrete, translation-unit–local globals.
+;
+; For each module (translation unit), the pass performs the following:
+;
+; 1. Creates a null-terminated, internal constant string global
+; (`__loadtime_comment_str`) containing the copyright text in
+; `__loadtime_comment` section.
+;
+; 2. Marks the string in `llvm.used` so it cannot be dropped by
+; optimization or LTO.
+;
+; 3. Attaches `!implicit.ref` metadata referencing the string to every
+; defined function in the module. The PowerPC AIX backend recognizes
+; this metadata and emits a `.ref` directive from the function to the
+; string, creating a concrete relocation that prevents the linker from
+; discarding it (as long as the referencing symbol is kept).
+
+target triple = "powerpc-ibm-aix"
+
+define void @f0() {
+entry:
+ ret void
+}
+define i32 @main() {
+entry:
+ ret i32 0
+}
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 1, !"wchar_size", i32 2}
+
+!comment_string.loadtime = !{!1}
+!1 = !{!"@(#) Copyright IBM 2025"}
+
+
+; ---- Globals--------------------------------------------
+; CHECK: @__loadtime_comment_str = internal unnamed_addr constant [24 x i8] c"@(#) Copyright IBM 2025\00", section "__loadtime_comment", align 1
+; Preservation in llvm.used sets
+; CHECK-NEXT: @llvm.used = appending global [1 x ptr] [ptr @__loadtime_comment_str], section "llvm.metadata"
+; CHECK-NOT: ![[copyright:[0-9]+]] = !{!"@(#) Copyright IBM 2025"}
+
+; 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]]
+
+; Verify metadata content
+; CHECK-O0: ![[MD]] = !{ptr @__loadtime_comment_str}
+; CHECK-ON: ![[MD]] = !{ptr @__loadtime_comment_str}
More information about the cfe-commits
mailing list