[clang] [llvm] [Darwin][Clang][PGO] Pass CSPGO related flags to ld when using libLTO.dylib (PR #195020)
Henry Jiang via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 29 23:47:35 PDT 2026
https://github.com/mustartt created https://github.com/llvm/llvm-project/pull/195020
Context-sensitive PGO is currently broken when using libLTO.dylib with ld on Darwin.
1. `Config.RunCSIRInstr` was not set, and was always false.
2. `ThinLTOCodeGenerator::optimizeModule` always passes an empty PGOOptions.
3. CSPGO flags were only passed for LLD. Now pass to the linker with `-mllvm` prefix.
>From 8e62ab256d7101c68a8f22b60dac16ec11c2d7f6 Mon Sep 17 00:00:00 2001
From: Henry Jiang <henry_jiang2 at apple.com>
Date: Wed, 29 Apr 2026 23:36:56 -0700
Subject: [PATCH] [Darwin] Fix clang driver's invocation for cspgo
---
clang/lib/Driver/ToolChains/Darwin.cpp | 18 ++++++++++++++
clang/test/Driver/cspgo-lto.c | 14 +++++++++++
llvm/lib/LTO/LTOCodeGenerator.cpp | 7 ++++--
llvm/lib/LTO/ThinLTOCodeGenerator.cpp | 12 ++++++++++
.../llvm-lto/Inputs/cspgo-noncs.proftext | 1 +
llvm/test/tools/llvm-lto/cspgo-generate.ll | 20 ++++++++++++++++
.../tools/llvm-lto/cspgo-thinlto-generate.ll | 24 +++++++++++++++++++
7 files changed, 94 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/tools/llvm-lto/Inputs/cspgo-noncs.proftext
create mode 100644 llvm/test/tools/llvm-lto/cspgo-generate.ll
create mode 100644 llvm/test/tools/llvm-lto/cspgo-thinlto-generate.ll
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index 61b779c60b90f..5045d21c4e262 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -493,6 +493,24 @@ void darwin::Linker::AddLinkArgs(Compilation &C, const ArgList &Args,
CmdArgs.push_back(
Args.MakeArgString(Twine("--codegen-data-generate-path=") +
CodeGenDataGenArg->getValue()));
+ } else {
+ if (auto *CSPGOGenerateArg = getLastCSProfileGenerateArg(Args)) {
+ SmallString<128> Path(CSPGOGenerateArg->getNumValues() == 0
+ ? ""
+ : CSPGOGenerateArg->getValue());
+ llvm::sys::path::append(Path, "default_%m.profraw");
+ CmdArgs.push_back("-mllvm");
+ CmdArgs.push_back("-cs-profile-generate");
+ CmdArgs.push_back("-mllvm");
+ CmdArgs.push_back(Args.MakeArgString(Twine("-cs-profile-path=") + Path));
+ } else if (auto *ProfileUseArg = getLastProfileUseArg(Args)) {
+ SmallString<128> Path(
+ ProfileUseArg->getNumValues() == 0 ? "" : ProfileUseArg->getValue());
+ if (Path.empty() || llvm::sys::fs::is_directory(Path))
+ llvm::sys::path::append(Path, "default.profdata");
+ CmdArgs.push_back("-mllvm");
+ CmdArgs.push_back(Args.MakeArgString(Twine("-cs-profile-path=") + Path));
+ }
}
}
diff --git a/clang/test/Driver/cspgo-lto.c b/clang/test/Driver/cspgo-lto.c
index 232d21bbf948a..4129298e652e4 100644
--- a/clang/test/Driver/cspgo-lto.c
+++ b/clang/test/Driver/cspgo-lto.c
@@ -18,3 +18,17 @@
// DARWIN-GEN1-SAME: "--cs-profile-path=default_%m.profraw"
// DARWIN-GEN2: "--cs-profile-generate"
// DARWIN-GEN2-SAME: "--cs-profile-path=directory{{(/|\\\\)}}default_%m.profraw"
+
+// RUN: %clang --target=arm64-apple-macos -### %t.o -flto=thin -fcs-profile-generate 2>&1 | FileCheck %s --check-prefix=DARWIN-LD-GEN1
+// RUN: %clang --target=arm64-apple-macos -### %t.o -flto=thin -fcs-profile-generate=directory 2>&1 | FileCheck %s --check-prefix=DARWIN-LD-GEN2
+
+// DARWIN-LD-GEN1: "-mllvm" "-cs-profile-generate"
+// DARWIN-LD-GEN1-SAME: "-mllvm" "-cs-profile-path=default_%m.profraw"
+// DARWIN-LD-GEN2: "-mllvm" "-cs-profile-generate"
+// DARWIN-LD-GEN2-SAME: "-mllvm" "-cs-profile-path=directory{{(/|\\\\)}}default_%m.profraw"
+
+// RUN: %clang --target=arm64-apple-macos -### %t.o -flto=thin -fprofile-use 2>&1 | FileCheck %s --check-prefix=DARWIN-LD-USE1
+// RUN: %clang --target=arm64-apple-macos -### %t.o -flto=thin -fprofile-use=a.profdata 2>&1 | FileCheck %s --check-prefix=DARWIN-LD-USE2
+
+// DARWIN-LD-USE1: "-mllvm" "-cs-profile-path=default.profdata"
+// DARWIN-LD-USE2: "-mllvm" "-cs-profile-path=a.profdata"
diff --git a/llvm/lib/LTO/LTOCodeGenerator.cpp b/llvm/lib/LTO/LTOCodeGenerator.cpp
index 46be71da5a092..321826618bc69 100644
--- a/llvm/lib/LTO/LTOCodeGenerator.cpp
+++ b/llvm/lib/LTO/LTOCodeGenerator.cpp
@@ -111,11 +111,11 @@ static cl::opt<std::string> AIXSystemAssemblerPath(
cl::desc("Path to a system assembler, picked up on AIX only"),
cl::value_desc("path"));
-static cl::opt<bool>
+cl::opt<bool>
LTORunCSIRInstr("cs-profile-generate",
cl::desc("Perform context sensitive PGO instrumentation"));
-static cl::opt<std::string>
+cl::opt<std::string>
LTOCSIRProfile("cs-profile-path",
cl::desc("Context sensitive profile file path"));
} // namespace llvm
@@ -557,6 +557,9 @@ bool LTOCodeGenerator::optimize() {
// libLTO parses options late, so re-set them here.
Context.setDiscardValueNames(LTODiscardValueNames);
+ Config.StatsFile = LTOStatsFile;
+ Config.RunCSIRInstr = LTORunCSIRInstr;
+ Config.CSIRProfile = LTOCSIRProfile;
auto DiagFileOrErr = lto::setupLLVMOptimizationRemarks(
Context, RemarksFilename, RemarksPasses, RemarksFormat,
diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
index 93b672ae7840c..41d83a591a37b 100644
--- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
+++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp
@@ -79,6 +79,8 @@ extern cl::opt<bool> RemarksWithHotness;
extern cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser>
RemarksHotnessThreshold;
extern cl::opt<std::string> RemarksFormat;
+extern cl::opt<bool> LTORunCSIRInstr;
+extern cl::opt<std::string> LTOCSIRProfile;
}
// Default to using all available threads in the system, but using only one
@@ -235,6 +237,16 @@ static void optimizeModule(Module &TheModule, TargetMachine &TM,
unsigned OptLevel, bool Freestanding,
bool DebugPassManager, ModuleSummaryIndex *Index) {
std::optional<PGOOptions> PGOOpt;
+ if (LTORunCSIRInstr) {
+ PGOOpt =
+ PGOOptions("", LTOCSIRProfile, "",
+ /*MemoryProfile=*/"", PGOOptions::IRUse,
+ PGOOptions::CSIRInstr, PGOOptions::ColdFuncOpt::Default);
+ } else if (!LTOCSIRProfile.empty()) {
+ PGOOpt = PGOOptions(LTOCSIRProfile, "", "",
+ /*MemoryProfile=*/"", PGOOptions::IRUse,
+ PGOOptions::CSIRUse, PGOOptions::ColdFuncOpt::Default);
+ }
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
diff --git a/llvm/test/tools/llvm-lto/Inputs/cspgo-noncs.proftext b/llvm/test/tools/llvm-lto/Inputs/cspgo-noncs.proftext
new file mode 100644
index 0000000000000..04a7c1c1a35aa
--- /dev/null
+++ b/llvm/test/tools/llvm-lto/Inputs/cspgo-noncs.proftext
@@ -0,0 +1 @@
+:ir
diff --git a/llvm/test/tools/llvm-lto/cspgo-generate.ll b/llvm/test/tools/llvm-lto/cspgo-generate.ll
new file mode 100644
index 0000000000000..d667cd3fde544
--- /dev/null
+++ b/llvm/test/tools/llvm-lto/cspgo-generate.ll
@@ -0,0 +1,20 @@
+; Test that CS-PGO instrumentation works through the old LTO C API codepath.
+; This validates the fix for a bug where LTOCodeGenerator::optimize() did not
+; re-read cl::opt values (cs-profile-generate, cs-profile-path) after
+; parseCodeGenDebugOptions(), causing Config.RunCSIRInstr to be stale.
+;
+; RUN: llvm-profdata merge %S/Inputs/cspgo-noncs.proftext -o %t.profdata
+; RUN: opt -module-summary %s -o %t.o
+; RUN: llvm-lto -cs-profile-generate -cs-profile-path=%t.profdata \
+; RUN: -exported-symbol=_main -o %t.lto.o %t.o
+; RUN: llvm-nm %t.lto.o | FileCheck %s
+;
+; CHECK: __profc_main
+
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "arm64-apple-macosx14.0.0"
+
+define i32 @main() {
+entry:
+ ret i32 0
+}
diff --git a/llvm/test/tools/llvm-lto/cspgo-thinlto-generate.ll b/llvm/test/tools/llvm-lto/cspgo-thinlto-generate.ll
new file mode 100644
index 0000000000000..670b5e82cfe4d
--- /dev/null
+++ b/llvm/test/tools/llvm-lto/cspgo-thinlto-generate.ll
@@ -0,0 +1,24 @@
+; Test that CS-PGO instrumentation works through ThinLTOCodeGenerator::optimizeModule().
+; This validates the fix for a bug where optimizeModule() created an empty
+; PGOOptions and never populated it from cl::opt flags, so the PassBuilder
+; never saw a CS instrumentation request.
+;
+; RUN: llvm-profdata merge %S/Inputs/cspgo-noncs.proftext -o %t.profdata
+; RUN: opt -module-summary %s -o %t.o
+; RUN: llvm-lto -thinlto-action=optimize \
+; RUN: -cs-profile-generate -cs-profile-path=%t.profdata \
+; RUN: -o %t.opt.bc %t.o
+; RUN: llvm-dis %t.opt.bc -o - | FileCheck %s
+;
+; CHECK: @__profc_main = private global
+; CHECK-LABEL: @main()
+; CHECK: %pgocount = load i64, ptr @__profc_main
+; CHECK: store i64 %{{.*}}, ptr @__profc_main
+
+target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
+target triple = "arm64-apple-macosx14.0.0"
+
+define i32 @main() {
+entry:
+ ret i32 0
+}
More information about the cfe-commits
mailing list