diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 9401377002223..ee59b6469eee3 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -1368,6 +1368,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; +def warn_pragma_comment_once : Warning<"'#pragma comment %0' " + "can be specified only once per translation unit - ignored">, + InGroup; // - #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 7bc0404db1bee..c58c33f95c193 100644 --- a/clang/lib/AST/TextNodeDumper.cpp +++ b/clang/lib/AST/TextNodeDumper.cpp @@ -2526,6 +2526,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 a6a1b84e278b9..9047eaa400b72 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -42,6 +42,7 @@ #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" @@ -1569,6 +1570,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()); @@ -3402,6 +3409,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, @@ -7579,6 +7611,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 a253bcda2d06c..895f91691cae3 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 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 7c2b9280f0b76..81cb3fb6efe44 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(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(); } @@ -3202,13 +3205,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(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(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; @@ -3220,6 +3231,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; @@ -3246,6 +3269,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/docs/LangRef.rst b/llvm/docs/LangRef.rst index 02865f8a29c67..518b0f9d48a54 100644 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -8670,6 +8670,37 @@ denoting if the type contains a pointer. !0 = !{!"", i1 } +'``implicit.ref``' Metadata +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +The ``implicit.ref`` metadata may be attached to a function or global variable +definition with a single argument that references a global object. +This is typically used when there is some implicit dependence between the +symbols that is otherwise opaque to the linker. One such example is metadata +which is accessed by a runtime with associated ``__start_`` and +``__stop_`` symbols. + +It does not have any effect on non-XCOFF targets. + +This metadata lowers to the .ref assembly directive which will add a relocation +representing an implicit reference from the section the global belongs to, to +the associated symbol. This link will keep the referenced symbol alive if the +section is not garbage collected. More than one ref node can be attached +to the same function or global variable. + + +Example: + +.. code-block:: text + + @a = global i32 1 + @b = global i32 2 + @c = global i32 3, section "abc", !implicit.ref !0, !implicit.ref !1 + !0 = !{ptr @a} + !1 = !{ptr @b} + + Module Flags Metadata ===================== diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def index 74746cced6f23..01d22bdd7c996 100644 --- a/llvm/include/llvm/IR/FixedMetadataKinds.def +++ b/llvm/include/llvm/IR/FixedMetadataKinds.def @@ -57,3 +57,4 @@ LLVM_FIXED_MD_KIND(MD_callee_type, "callee_type", 42) LLVM_FIXED_MD_KIND(MD_nofree, "nofree", 43) LLVM_FIXED_MD_KIND(MD_captures, "captures", 44) LLVM_FIXED_MD_KIND(MD_alloc_token, "alloc_token", 45) +LLVM_FIXED_MD_KIND(MD_implicit_ref, "implicit.ref", 46) 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 { +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/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index a1e14d8f25bf7..6f2b056be492e 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -769,6 +769,31 @@ void Verifier::visitGlobalValue(const GlobalValue &GV) { DL.getIntPtrType(GO->getType()), RangeLikeMetadataKind::AbsoluteSymbol); } + + if (GO->hasMetadata(LLVMContext::MD_implicit_ref)) { + Check(!GO->isDeclaration(), + "ref metadata must not be placed on a declaration", GO); + + SmallVector MDs; + GO->getMetadata(LLVMContext::MD_implicit_ref, MDs); + for (const MDNode *MD : MDs) { + Check(MD->getNumOperands() == 1, "ref metadata must have one operand", + &GV, MD); + const Metadata *Op = MD->getOperand(0).get(); + const auto *VM = dyn_cast_or_null(Op); + Check(VM, "ref metadata must be ValueAsMetadata", GO, MD); + if (VM) { + Check(isa(VM->getValue()->getType()), + "ref value must be pointer typed", GV, MD); + + const Value *Stripped = VM->getValue()->stripPointerCastsAndAliases(); + Check(isa(Stripped) || isa(Stripped), + "ref metadata must point to a GlobalObject", GO, Stripped); + Check(Stripped != GO, "values should not reference themselves", GO, + MD); + } + } + } } Check(!GV.hasAppendingLinkage() || isa(GV), diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp index f5281ea69b512..df95d76bfc1b1 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 c6beb3fdf09bd..99764994bfcc3 100644 --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -135,6 +135,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" @@ -1454,6 +1455,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) @@ -2274,6 +2278,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 074c328ef0931..16f5cdb8f58fb 100644 --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -61,6 +61,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/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp index 122738caa6827..718d51c5a0673 100644 --- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp +++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp @@ -305,6 +305,8 @@ class PPCAIXAsmPrinter : public PPCAsmPrinter { void emitTTypeReference(const GlobalValue *GV, unsigned Encoding) override; void emitModuleCommandLines(Module &M) override; + + void emitRefMetadata(const GlobalObject *); }; } // end anonymous namespace @@ -2801,6 +2803,10 @@ void PPCAIXAsmPrinter::emitGlobalVariableHelper(const GlobalVariable *GV) { // Switch to the containing csect. OutStreamer->switchSection(Csect); + if (GV->hasMetadata(LLVMContext::MD_implicit_ref)) { + emitRefMetadata(GV); + } + const DataLayout &DL = GV->getDataLayout(); // Handle common and zero-initialized local symbols. @@ -2893,10 +2899,16 @@ void PPCAIXAsmPrinter::emitFunctionEntryLabel() { if (!TM.getFunctionSections() || MF->getFunction().hasSection()) PPCAsmPrinter::emitFunctionEntryLabel(); + const Function *F = &MF->getFunction(); + // Emit aliasing label for function entry point label. - for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()]) + for (const GlobalAlias *Alias : GOAliasMap[F]) OutStreamer->emitLabel( getObjFileLowering().getFunctionEntryPointSymbol(Alias, TM)); + + if (F->hasMetadata(LLVMContext::MD_implicit_ref)) { + emitRefMetadata(F); + } } void PPCAIXAsmPrinter::emitPGORefs(Module &M) { @@ -3336,6 +3348,19 @@ void PPCAIXAsmPrinter::emitTTypeReference(const GlobalValue *GV, OutStreamer->emitIntValue(0, GetSizeOfEncodedValue(Encoding)); } +void PPCAIXAsmPrinter::emitRefMetadata(const GlobalObject *GO) { + SmallVector MDs; + GO->getMetadata(LLVMContext::MD_implicit_ref, MDs); + assert(MDs.size() && "Expected asscoiated metadata nodes"); + + for (const MDNode *MD : MDs) { + const ValueAsMetadata *VAM = cast(MD->getOperand(0).get()); + const GlobalValue *GV = cast(VAM->getValue()); + MCSymbol *Referenced = TM.getSymbol(GV); + OutStreamer->emitXCOFFRefDirective(Referenced); + } +} + // Return a pass that prints the PPC assembly code for a MachineFunction to the // given output stream. static AsmPrinter * 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/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp index f49fbf8807bac..aa902f687d8aa 100644 --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -2830,6 +2830,15 @@ void llvm::InlineFunctionImpl(CallBase &CB, InlineFunctionInfo &IFI, // Propagate metadata on the callsite if necessary. PropagateCallSiteMetadata(CB, FirstNewBlock, Caller->end()); + // Propagate implicit ref metadata. + if (CalledFunc->hasMetadata(LLVMContext::MD_implicit_ref)) { + SmallVector MDs; + CalledFunc->getMetadata(LLVMContext::MD_implicit_ref, MDs); + for (MDNode *MD : MDs) { + Caller->addMetadata(LLVMContext::MD_implicit_ref, *MD); + } + } + // Register any cloned assumptions. if (IFI.GetAssumptionCache) for (BasicBlock &NewBlock : 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 + 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(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 86090324c770c..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' -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),alloc-token,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/CodeGen/PowerPC/aix-func-ref.ll b/llvm/test/CodeGen/PowerPC/aix-func-ref.ll new file mode 100644 index 0000000000000..5cb0575f370e1 --- /dev/null +++ b/llvm/test/CodeGen/PowerPC/aix-func-ref.ll @@ -0,0 +1,55 @@ +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | \ +; RUN: FileCheck %s -check-prefixes=NOFSECTS,CHECK + +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff --function-sections < %s | \ +; RUN: FileCheck %s -check-prefixes=FSECTS,CHECK + +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s +; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ %s + +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff --function-sections -filetype=obj -o %t.o < %s +; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=FSECTOBJ %s + +@a = global i32 1 +@b = global i32 2 +@c = global i32 3 + +define i32 @foo() !implicit.ref !0 { + ret i32 0 +} + +define i32 @bar() !implicit.ref !1 !implicit.ref !2 { + ret i32 0 +} + +!0 = !{ptr @a} +!1 = !{ptr @b} +!2 = !{ptr @c} + +; NOFSECTS: .foo: +; FSECTS: .csect .foo[PR] +; CHECK: .ref a[RW] + +; NOFSECTS: .bar: +; FSECTS: .csect .bar[PR] +; CHECK: .ref b[RW] +; CHECK: .ref c[RW] + +; OBJ: Disassembly of section .text: +; OBJ: .foo: +; OBJ: li 3, 0 +; OBJ: R_REF {{.*}} a[RW] +; OBJ: R_REF {{.*}} b[RW] +; OBJ: R_REF {{.*}} c[RW] +; OBJ: blr +; OBJ: .bar + +; FSECTOBJ: .foo[PR]: +; FSECTOBJ: li 3, 0 +; FSECTOBJ: R_REF {{.*}} a[RW] +; FSECTOBJ: blr +; FSECTOBJ: .bar[PR]: +; FSECTOBJ: li 3, 0 +; FSECTOBJ: R_REF {{.*}} b[RW] +; FSECTOBJ: R_REF {{.*}} c[RW] +; FSECTOBJ: blr diff --git a/llvm/test/CodeGen/PowerPC/aix-ref-metadata.ll b/llvm/test/CodeGen/PowerPC/aix-ref-metadata.ll new file mode 100644 index 0000000000000..7dc9466a7ba9b --- /dev/null +++ b/llvm/test/CodeGen/PowerPC/aix-ref-metadata.ll @@ -0,0 +1,77 @@ +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | FileCheck %s +; RUN: llc -data-sections=false -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff < %s | FileCheck -check-prefix=NODATA %s + +; RUN: llc -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s +; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ %s + +; RUN: llc -data-sections=false -verify-machineinstrs -mtriple powerpc-ibm-aix-xcoff -filetype=obj -o %t.o < %s +; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck -check-prefix=OBJ-NODATA %s + +@a = global i32 1 +@b = global i32 2 +@c = global i32 3, section "custom_section_c" +@d = global i32 4, !implicit.ref !0 +@e = constant i32 5, !implicit.ref !1, !implicit.ref!2 +@f = global i32 6, section "custom_section_f", !implicit.ref !1 + + +!0 = !{ptr @a} +!1 = !{ptr @b} +!2 = !{ptr @c} + +; CHECK: .csect d[RW] +; CHECK: .ref a[RW] + +; CHECK: .csect e[RO] +; CHECK: .ref b[RW] +; CHECK: .ref c + +; CHECK: .csect custom_section_f[RW] +; CHECK: .ref b[RW] + +; NODATA: .csect .data[RW] +; NODATA-NOT: .csect +; NODATA: .globl a +; NODATA-NOT: .csect +; NODATA: .globl b +; NODATA: .csect custom_section_c[RW] +; NODATA: .globl c +; NODATA: .csect .data[RW] +; NODATA: .ref a +; NODATA: .globl d +; NODATA: .csect .rodata[RO] +; NODATA: .ref b +; NODATA: .ref c +; NODATA: .globl e +; NODATA: .csect custom_section_f[RW] +; NODATA: .ref b +; NODATA: .globl f + +; OBJ: Disassembly of section .text: + +; OBJ: e[RO]: +; OBJ: R_REF {{.*}} b[RW] +; OBJ: R_REF {{.*}} c + +; OBJ: Disassembly of section .data: +; OBJ: a[RW]: +; OBJ: b[RW]: +; OBJ: c: +; OBJ: d[RW]: +; OBJ: R_REF {{.*}} a[RW] +; OBJ: f: +; OBJ: R_REF {{.*}} b[RW] + +; OBJ-NODATA: Disassembly of section .text: +; OBJ-NODATA: e: +; OBJ-NODATA: R_REF {{.*}} b +; OBJ-NODATA: R_REF {{.*}} c + +; OBJ-NODATA: Disassembly of section .data: +; OBJ-NODATA: a: +; OBJ-NODATA: R_REF {{.*}} a +; OBJ-NODATA: b: +; OBJ-NODATA: d: +; OBJ-NODATA: c: +; OBJ-NODATA: f: +; OBJ-NODATA: R_REF {{.*}} b diff --git a/llvm/test/Other/new-pm-defaults.ll b/llvm/test/Other/new-pm-defaults.ll index f074b2fdd3ab8..979a7897b4c02 100644 --- a/llvm/test/Other/new-pm-defaults.ll +++ b/llvm/test/Other/new-pm-defaults.ll @@ -238,6 +238,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 b0d08316de4f0..d03b15d276e0b 100644 --- a/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-postlink-defaults.ll @@ -162,6 +162,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 6b3e82a752899..b5b0d55bf410c 100644 --- a/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-postlink-pgo-defaults.ll @@ -146,6 +146,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 88dc18f605ce2..57561975769d0 100644 --- a/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll +++ b/llvm/test/Other/new-pm-thinlto-postlink-samplepgo-defaults.ll @@ -155,6 +155,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/FunctionSpecialization/function-specialization-implicit-ref.ll b/llvm/test/Transforms/FunctionSpecialization/function-specialization-implicit-ref.ll new file mode 100644 index 0000000000000..1e38e5c80fa5f --- /dev/null +++ b/llvm/test/Transforms/FunctionSpecialization/function-specialization-implicit-ref.ll @@ -0,0 +1,43 @@ +; RUN: opt -passes="ipsccp" -S -force-specialization < %s 2>&1 | FileCheck %s + +@a = global i32 1 + +define i64 @main(i64 %x, i1 %flag) { +entry: + br i1 %flag, label %plus, label %minus + +plus: + %tmp0 = call i64 @compute(i64 %x, ptr @plus) + br label %merge + +minus: + %tmp1 = call i64 @compute(i64 %x, ptr @minus) + br label %merge + +merge: + %tmp2 = phi i64 [ %tmp0, %plus ], [ %tmp1, %minus] + ret i64 %tmp2 +} + +define internal i64 @compute(i64 %x, ptr %binop) !implicit.ref !0 { +entry: + %tmp0 = call i64 %binop(i64 %x) + ret i64 %tmp0 +} + +define internal i64 @plus(i64 %x) { +entry: + %tmp0 = add i64 %x, 1 + ret i64 %tmp0 +} + +define internal i64 @minus(i64 %x) { +entry: + %tmp0 = sub i64 %x, 1 + ret i64 %tmp0 +} + +!0 = !{ptr @a} + +; CHECK: @compute.specialized.1(i64 %x, ptr %binop) !implicit.ref !0 +; CHECK: @compute.specialized.2(i64 %x, ptr %binop) !implicit.ref !0 diff --git a/llvm/test/Transforms/Inline/inline-ref-metadata.ll b/llvm/test/Transforms/Inline/inline-ref-metadata.ll new file mode 100644 index 0000000000000..a252ee4ec36ce --- /dev/null +++ b/llvm/test/Transforms/Inline/inline-ref-metadata.ll @@ -0,0 +1,50 @@ +; RUN: opt < %s -passes='cgscc(inline)' -S | FileCheck %s + +@a = global i32 1 +@b = global i32 2 +@c = global double 3.141593e+00 + +define i32 @callee1() !implicit.ref !0 { + ret i32 0 +} + +define i32 @callee2() !implicit.ref !1 { + ret i32 1 +} + +define i32 @callee3() { + %i = call i32 @callee2() + ret i32 %i +} +; CHECK: @callee3() !implicit.ref !1 + +define i32 @caller1() { + %i = call i32 @callee1() + ret i32 %i +} +; CHECK: @caller1() !implicit.ref !0 + +define i32 @caller2() !implicit.ref !2 { + %i = call i32 @callee1() + ret i32 %i +} +; CHECK: @caller2() !implicit.ref !2 !implicit.ref !0 + +define i32 @caller3() { + %i = call i32 @caller4() + ret i32 %i +} +; CHECK: @caller3() !implicit.ref !0 !implicit.ref !1 + +define i32 @caller4() { + %a = call i32 @callee1() + %b = call i32 @callee2() + %add = add i32 %a, %b + ret i32 %add +} +; CHECK: @caller4() !implicit.ref !0 !implicit.ref !1 + +!0 = !{ptr @a} +!1 = !{ptr @b} +!2 = !{ptr @c} + 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} diff --git a/llvm/test/Verifier/ref-func.ll b/llvm/test/Verifier/ref-func.ll new file mode 100644 index 0000000000000..e3cddbb5ee20f --- /dev/null +++ b/llvm/test/Verifier/ref-func.ll @@ -0,0 +1,12 @@ +; RUN: llvm-as < %s -o /dev/null 2>&1 + +@a = global i32 1 +@b = global i32 2 +@c = global i32 3, !implicit.ref !0 + +define i32 @foo() !implicit.ref !1 { + ret i32 0 +} + +!0 = !{ptr @a} +!1 = !{ptr @b} diff --git a/llvm/test/Verifier/ref.ll b/llvm/test/Verifier/ref.ll new file mode 100644 index 0000000000000..58e195320583e --- /dev/null +++ b/llvm/test/Verifier/ref.ll @@ -0,0 +1,31 @@ +; RUN: not llvm-as -disable-output < %s -o /dev/null 2>&1 | FileCheck %s + +@a = global i32 1, !implicit.ref !0 +@b = global i32 2, !implicit.ref !1 +@c = global i32 3, !implicit.ref !1, !implicit.ref !2 +@d = global i32 4, !implicit.ref !3 +@e = external global i32, !implicit.ref !1 + +!0 = !{i32 1} +!1 = !{ptr @b} +!2 = !{!"Hello World!"} +!3 = !{ptr @c, ptr @a} + +; CHECK: ref value must be pointer typed +; CHECK: ptr @a +; CHECK: !0 = !{i32 1} + +; CHECK: values should not reference themselves +; CHECK: ptr @b +; CHECK: !1 = !{ptr @b} + +; CHECK: ref metadata must be ValueAsMetadata +; CHECK: ptr @c +; CHECK: !2 = !{!"Hello World!"} + +; CHECK: ref metadata must have one operand +; CHECK: ptr @d +; CHECK: !3 = !{ptr @c, ptr @a} + +; CHECK: ref metadata must not be placed on a declaration +; CHECK: @e