[clang] [lld] [llvm] [LTO] Invoke PreCodeGenCallback of pass plugins in the LTO backend (PR #231516)
Stefan Gränitz via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 14 15:51:00 PDT 2026
https://github.com/weliveindetail updated https://github.com/llvm/llvm-project/pull/231516
>From fd90348d281d2297f6942b1f01cbf69bdbb10bbb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Mon, 21 Sep 2026 15:57:03 +0200
Subject: [PATCH 1/2] [LTO] Support PreCodeGenCallback for pass-plugins in lld
and clang
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
clang/test/CodeGen/codegen-plugins-thinlto.c | 21 ++++++++++
lld/test/ELF/lto/pre-codegen-plugin.ll | 28 +++++++++++++
llvm/lib/LTO/LTOBackend.cpp | 44 +++++++++++++++-----
llvm/test/Feature/codegen-plugin.ll | 10 +++++
4 files changed, 92 insertions(+), 11 deletions(-)
create mode 100644 clang/test/CodeGen/codegen-plugins-thinlto.c
create mode 100644 lld/test/ELF/lto/pre-codegen-plugin.ll
diff --git a/clang/test/CodeGen/codegen-plugins-thinlto.c b/clang/test/CodeGen/codegen-plugins-thinlto.c
new file mode 100644
index 00000000000000..9f5fae5c1c65b4
--- /dev/null
+++ b/clang/test/CodeGen/codegen-plugins-thinlto.c
@@ -0,0 +1,21 @@
+// REQUIRES: plugins, llvm-examples
+// UNSUPPORTED: target={{.*windows.*}}
+// Plugins are currently broken on AIX, at least in the CI.
+// XFAIL: target={{.*}}-aix{{.*}}
+
+// RUN: %clang_cc1 -O2 -flto=thin -emit-llvm-bc %s -o %t.o
+// RUN: llvm-lto -thinlto -o %t %t.o
+// RUN: %clang_cc1 -O2 -x ir %t.o -fthinlto-index=%t.thinlto.bc -S -o - -fpass-plugin=%llvmshlibdir/Bye%pluginext 2>&1 | FileCheck %s --check-prefix=CHECK-INACTIVE
+// RUN: %clang_cc1 -O2 -x ir %t.o -fthinlto-index=%t.thinlto.bc -S -o - -fpass-plugin=%llvmshlibdir/Bye%pluginext -mllvm -last-words | FileCheck %s --check-prefix=CHECK-ACTIVE
+// RUN: %clang_cc1 -O2 -x ir %t.o -fthinlto-index=%t.thinlto.bc -emit-llvm -o - -fpass-plugin=%llvmshlibdir/Bye%pluginext -mllvm -last-words | FileCheck %s --check-prefix=CHECK-LLVM
+// RUN: not %clang_cc1 -O2 -x ir %t.o -fthinlto-index=%t.thinlto.bc -emit-obj -o - -fpass-plugin=%llvmshlibdir/Bye%pluginext -mllvm -last-words 2>&1 | FileCheck %s --check-prefix=CHECK-ERR
+
+// CHECK-INACTIVE-NOT: Bye
+// CHECK-INACTIVE: f:
+// CHECK-ACTIVE: CodeGen Bye
+// CHECK-LLVM: define{{.*}} i32 @f
+// CHECK-ERR: error: last words unsupported for binary output
+
+int f(int x) {
+ return x;
+}
diff --git a/lld/test/ELF/lto/pre-codegen-plugin.ll b/lld/test/ELF/lto/pre-codegen-plugin.ll
new file mode 100644
index 00000000000000..880d422adff9d0
--- /dev/null
+++ b/lld/test/ELF/lto/pre-codegen-plugin.ll
@@ -0,0 +1,28 @@
+; REQUIRES: x86, plugins, examples
+; UNSUPPORTED: target={{.*windows.*}}
+; Plugins are currently broken on AIX, at least in the CI.
+; XFAIL: target={{.*}}-aix{{.*}}
+
+; RUN: opt %s -o %t.o
+; RUN: ld.lld -shared --lto-emit-asm -%loadnewpmbye %t.o -o %t.inactive 2>&1 | count 0
+; RUN: FileCheck %s --check-prefix=INACTIVE < %t.inactive.lto.s
+; RUN: ld.lld -shared --lto-emit-asm -%loadnewpmbye -mllvm=%loadbye -mllvm=-last-words %t.o -o %t.active
+; RUN: FileCheck %s --check-prefix=ACTIVE < %t.active.lto.s
+; RUN: not ld.lld -shared -%loadnewpmbye -mllvm=%loadbye -mllvm=-last-words %t.o -o %t.err 2>&1 | FileCheck %s --check-prefix=ERR
+; RUN: ld.lld -shared --lto-emit-asm --lto-partitions=2 -%loadnewpmbye -mllvm=%loadbye -mllvm=-last-words %t.o -o %t.parallel
+; RUN: FileCheck %s --check-prefix=PARALLEL0 < %t.parallel.lto.s
+; RUN: FileCheck %s --check-prefix=PARALLEL1 < %t.parallel.lto.1.s
+
+; INACTIVE-NOT: Bye
+; INACTIVE: f:
+; ACTIVE: CodeGen Bye
+; ERR: error: last words unsupported for binary output
+; PARALLEL0: CodeGen Bye
+; PARALLEL1: CodeGen Bye
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i32 @f(i32 %x) {
+ ret i32 %x
+}
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 13b5e6e93335dc..3be710a7864bb4 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -199,23 +199,34 @@ Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath,
#include "llvm/Support/Extension.def"
#undef HANDLE_EXTENSION
+// Obtain dynamic pass-plugins in registration order: the ones requested by file
+// name followed by those pre-loaded in the host application
+static SmallVector<PassPlugin *, 2>
+getPassPlugins(const Config &Conf, SmallVectorImpl<PassPlugin> &Storage) {
+ for (const std::string &PluginFN : Conf.PassPluginFilenames) {
+ Expected<PassPlugin> Plugin = PassPlugin::load(PluginFN);
+ if (!Plugin)
+ reportFatalUsageError(Plugin.takeError());
+ Storage.push_back(std::move(*Plugin));
+ }
+
+ SmallVector<PassPlugin *, 2> Plugins(make_pointer_range(Storage));
+ append_range(Plugins, Conf.LoadedPassPlugins);
+ return Plugins;
+}
+
+// Call pass builder registration callbacks for all pass-plugins
static void RegisterPassPlugins(const Config &Conf, PassBuilder &PB) {
+ // Static built-in plugins
#define HANDLE_EXTENSION(Ext) \
get##Ext##PluginInfo().RegisterPassBuilderCallbacks(PB);
#include "llvm/Support/Extension.def"
#undef HANDLE_EXTENSION
- // Load requested pass plugins and let them register pass builder callbacks
- for (auto &PluginFN : Conf.PassPluginFilenames) {
- auto PassPlugin = PassPlugin::load(PluginFN);
- if (!PassPlugin)
- reportFatalUsageError(PassPlugin.takeError());
- PassPlugin->registerPassBuilderCallbacks(PB);
- }
-
- // Register already loaded plugins
- for (auto *LoadedPlugin : Conf.LoadedPassPlugins)
- LoadedPlugin->registerPassBuilderCallbacks(PB);
+ // Dynamic plugins
+ SmallVector<PassPlugin, 2> Storage;
+ for (const PassPlugin *Plugin : getPassPlugins(Conf, Storage))
+ Plugin->registerPassBuilderCallbacks(PB);
}
static std::unique_ptr<TargetMachine>
@@ -479,6 +490,17 @@ static void codegen(const Config &Conf, TargetMachine *TM,
std::unique_ptr<CachedFileStream> &Stream = *StreamOrErr;
TM->Options.ObjectFilenameForDebug = Stream->ObjectPathName;
+ // Allow plugins to handle or even take over code generation entirely.
+ SmallVector<PassPlugin, 2> PluginStorage;
+ for (PassPlugin *Plugin : getPassPlugins(Conf, PluginStorage)) {
+ if (Plugin->invokePreCodeGenCallback(Mod, *TM, Conf.CGFileType,
+ *Stream->OS)) {
+ if (Error Err = Stream->commit())
+ reportFatalUsageError(std::move(Err));
+ return;
+ }
+ }
+
// Create the codegen pipeline in its own scope so it gets deleted before
// Stream->commit() is called. The commit function of CacheStream deletes
// the raw stream, which is too early as streamers (e.g. MCAsmStreamer)
diff --git a/llvm/test/Feature/codegen-plugin.ll b/llvm/test/Feature/codegen-plugin.ll
index 571c7e348520e9..bdbca8723c3afb 100644
--- a/llvm/test/Feature/codegen-plugin.ll
+++ b/llvm/test/Feature/codegen-plugin.ll
@@ -2,6 +2,15 @@
; RUN: llc < %s %loadnewpmbye | FileCheck %s --check-prefix=CHECK-ASM
; RUN: llc < %s %loadnewpmbye -plugin-arg=Bye,-last-words | FileCheck %s --check-prefix=CHECK-ACTIVE
; RUN: not llc < %s %loadnewpmbye -plugin-arg=Bye,-last-words -filetype=obj 2>&1 | FileCheck %s --check-prefix=CHECK-ERR
+
+; LTO tools do not support -plugin-arg yet, so load Bye early to register its options
+; RUN: llvm-as %s -o %t.bc
+; RUN: llvm-lto2 run %t.bc -o %t.inactive -r %t.bc,somefunk,plx -r %t.bc,junk,plx -filetype=asm %loadnewpmbye
+; RUN: FileCheck %s --check-prefix=CHECK-ASM < %t.inactive.0
+; RUN: llvm-lto2 run %t.bc -o %t.active -r %t.bc,somefunk,plx -r %t.bc,junk,plx -filetype=asm %loadbye %loadnewpmbye -last-words
+; RUN: FileCheck %s --check-prefix=CHECK-ACTIVE < %t.active.0
+; RUN: not llvm-lto2 run %t.bc -o %t.err -r %t.bc,somefunk,plx -r %t.bc,junk,plx -filetype=obj %loadbye %loadnewpmbye -last-words 2>&1 | FileCheck %s --check-prefix=CHECK-LTO2-ERR
+
; REQUIRES: plugins, examples
; UNSUPPORTED: target={{.*windows.*}}
; Plugins are currently broken on AIX, at least in the CI.
@@ -9,6 +18,7 @@
; CHECK-ASM: somefunk:
; CHECK-ACTIVE: CodeGen Bye
; CHECK-ERR: error: last words unsupported for binary output
+; CHECK-LTO2-ERR: last words unsupported for binary output
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"
>From 230a6d89d6f659c47bbbd7fe9a1d828de115a344 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <stefan.graenitz at gmail.com>
Date: Thu, 15 Oct 2026 00:49:44 +0200
Subject: [PATCH 2/2] Switch getPassPlugins() to PassPluginLibraryInfos
---
llvm/lib/LTO/LTOBackend.cpp | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 3be710a7864bb4..4b818f08eb6518 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -201,17 +201,17 @@ Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath,
// Obtain dynamic pass-plugins in registration order: the ones requested by file
// name followed by those pre-loaded in the host application
-static SmallVector<PassPlugin *, 2>
-getPassPlugins(const Config &Conf, SmallVectorImpl<PassPlugin> &Storage) {
+static SmallVector<PassPluginLibraryInfo, 0>
+getPassPlugins(const Config &Conf) {
+ SmallVector<PassPluginLibraryInfo, 0> Plugins;
for (const std::string &PluginFN : Conf.PassPluginFilenames) {
Expected<PassPlugin> Plugin = PassPlugin::load(PluginFN);
if (!Plugin)
reportFatalUsageError(Plugin.takeError());
- Storage.push_back(std::move(*Plugin));
+ Plugins.push_back(Plugin->getInfo());
}
-
- SmallVector<PassPlugin *, 2> Plugins(make_pointer_range(Storage));
- append_range(Plugins, Conf.LoadedPassPlugins);
+ for (const PassPlugin *Plugin : Conf.LoadedPassPlugins)
+ Plugins.push_back(Plugin->getInfo());
return Plugins;
}
@@ -224,9 +224,9 @@ static void RegisterPassPlugins(const Config &Conf, PassBuilder &PB) {
#undef HANDLE_EXTENSION
// Dynamic plugins
- SmallVector<PassPlugin, 2> Storage;
- for (const PassPlugin *Plugin : getPassPlugins(Conf, Storage))
- Plugin->registerPassBuilderCallbacks(PB);
+ for (const PassPluginLibraryInfo &Plugin : getPassPlugins(Conf))
+ if (Plugin.RegisterPassBuilderCallbacks)
+ Plugin.RegisterPassBuilderCallbacks(PB);
}
static std::unique_ptr<TargetMachine>
@@ -491,10 +491,9 @@ static void codegen(const Config &Conf, TargetMachine *TM,
TM->Options.ObjectFilenameForDebug = Stream->ObjectPathName;
// Allow plugins to handle or even take over code generation entirely.
- SmallVector<PassPlugin, 2> PluginStorage;
- for (PassPlugin *Plugin : getPassPlugins(Conf, PluginStorage)) {
- if (Plugin->invokePreCodeGenCallback(Mod, *TM, Conf.CGFileType,
- *Stream->OS)) {
+ for (const PassPluginLibraryInfo &Plugin : getPassPlugins(Conf)) {
+ if (Plugin.PreCodeGenCallback &&
+ Plugin.PreCodeGenCallback(Mod, *TM, Conf.CGFileType, *Stream->OS)) {
if (Error Err = Stream->commit())
reportFatalUsageError(std::move(Err));
return;
More information about the cfe-commits
mailing list