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