llvm-project icon indicating copy to clipboard operation
llvm-project copied to clipboard

Test entry-points for pass-plugins with Bye in clang, flang and lld

Open weliveindetail opened this issue 1 month ago • 10 comments

LLVM pass-plugins are loaded into various tools. Plugins can register passes at different stages of the pipeline, called entry-points. Depending on the pipeline's configuration, different entry-points will be exercised. AFAIK there is no documentation on which entry-points are supposed to be exercised under which conditions. This patch adds regression tests for the status quo in the major consumers of pass-plugins.

weliveindetail avatar Dec 16 '25 11:12 weliveindetail

@llvm/pr-subscribers-lld-elf

Author: Stefan Gränitz (weliveindetail)

Changes

LLVM pass-plugins are loaded into various tools. Plugins can register passes at different stages of the pipeline, called entry-points. Depending on the pipeline's configuration, different entry-points will be exercised. AFAIK there is no documentation on which entry-points are supposed to be exercised under which conditions. This patch adds regression tests for the status quo in the major consumers of pass-plugins.


Full diff: https://github.com/llvm/llvm-project/pull/172463.diff

10 Files Affected:

  • (modified) clang/test/CMakeLists.txt (+6)
  • (added) clang/test/CodeGen/pass-plugins-entrypoints.c (+66)
  • (added) clang/test/Interpreter/pass-plugins.cpp (+9)
  • (modified) clang/test/lit.cfg.py (+2)
  • (modified) clang/test/lit.site.cfg.py.in (+1)
  • (added) flang/test/Integration/pass-plugins-entrypoints.f90 (+64)
  • (modified) flang/test/lit.cfg.py (+3-2)
  • (modified) flang/test/lit.site.cfg.py.in (+1-1)
  • (modified) lld/test/ELF/lto/ltopasses-extension.ll (+36-1)
  • (modified) llvm/examples/Bye/Bye.cpp (+101-15)
diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt
index bcb6bd68fafc2..05b0ee42da42b 100644
--- a/clang/test/CMakeLists.txt
+++ b/clang/test/CMakeLists.txt
@@ -134,6 +134,12 @@ if(CLANG_BUILD_EXAMPLES AND CLANG_PLUGIN_SUPPORT)
     )
 endif ()
 
+if(LLVM_BUILD_EXAMPLES AND NOT WIN32)
+  list(APPEND CLANG_TEST_DEPS
+    Bye
+    )
+endif()
+
 if(LLVM_INCLUDE_SPIRV_TOOLS_TESTS)
   list(APPEND CLANG_TEST_DEPS
     spirv-dis
diff --git a/clang/test/CodeGen/pass-plugins-entrypoints.c b/clang/test/CodeGen/pass-plugins-entrypoints.c
new file mode 100644
index 0000000000000..90182961b8057
--- /dev/null
+++ b/clang/test/CodeGen/pass-plugins-entrypoints.c
@@ -0,0 +1,66 @@
+// REQUIRES: plugins, llvm-examples
+
+// Entry-points in default and -O0 pipeline
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=full -O0 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=thin -O0 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// EP:     PipelineStart
+// EP:     PipelineEarlySimplification
+// EP-NOT: Peephole
+// EP-NOT: ScalarOptimizerLate
+// EP-NOT: Peephole
+// EP:     OptimizerEarly
+// EP-NOT: Vectorizer
+// EP:     OptimizerLast
+
+// Entry-points in optimizer pipeline
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=full \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -ffat-lto-objects \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// EP-OPT: PipelineStart
+// EP-OPT: PipelineEarlySimplification
+// EP-OPT: Peephole
+// EP-OPT: ScalarOptimizerLate
+// EP-OPT: Peephole
+// EP-OPT: OptimizerEarly
+// EP-OPT: VectorizerStart
+// EP-OPT: VectorizerEnd
+// EP-OPT: OptimizerLast
+
+// FIXME: Thin-LTO does not invoke vectorizer callbacks
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=thin \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-LTO-THIN %s
+//
+// EP-LTO-THIN:     PipelineStart
+// EP-LTO-THIN:     PipelineEarlySimplification
+// EP-LTO-THIN:     Peephole
+// EP-LTO-THIN:     ScalarOptimizerLate
+// EP-LTO-THIN:     OptimizerEarly
+// EP-LTO-THIN-NOT: Vectorizer
+// EP-LTO-THIN:     OptimizerLast
+
+int f(int x) {
+  return x;
+}
diff --git a/clang/test/Interpreter/pass-plugins.cpp b/clang/test/Interpreter/pass-plugins.cpp
new file mode 100644
index 0000000000000..4d445807c3d70
--- /dev/null
+++ b/clang/test/Interpreter/pass-plugins.cpp
@@ -0,0 +1,9 @@
+// RUN: cat %s | clang-repl -Xcc -fpass-plugin=%plugindir/pypass-plugin%pluginext \
+// RUN:                     -Xcc -load=%plugindir/pypass-plugin%pluginext \
+// RUN:                     -Xcc -Xclang -Xcc -mllvm -Xcc -wave-goodbye | FileCheck %s
+// REQUIRES: plugins, llvm-examples
+
+int i = 10;
+%quit
+
+// CHECK: Bye
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index 52b275c095475..a622f5335354a 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -126,6 +126,8 @@
 
 if config.clang_examples:
     config.available_features.add("examples")
+if config.llvm_examples:
+    config.available_features.add("llvm-examples")
 
 
 def have_host_out_of_process_jit_feature_support():
diff --git a/clang/test/lit.site.cfg.py.in b/clang/test/lit.site.cfg.py.in
index f50953a93a412..8e0ecdbe07805 100644
--- a/clang/test/lit.site.cfg.py.in
+++ b/clang/test/lit.site.cfg.py.in
@@ -28,6 +28,7 @@ config.clang_staticanalyzer_z3 = @LLVM_WITH_Z3@
 config.clang_staticanalyzer_z3_mock = @TEST_WITH_Z3_MOCK@
 config.clang_enable_cir = @CLANG_ENABLE_CIR@
 config.clang_examples = @CLANG_BUILD_EXAMPLES@
+config.llvm_examples = @LLVM_BUILD_EXAMPLES@
 config.enable_shared = @ENABLE_SHARED@
 config.enable_backtrace = @ENABLE_BACKTRACES@
 config.enable_threads = @LLVM_ENABLE_THREADS@
diff --git a/flang/test/Integration/pass-plugins-entrypoints.f90 b/flang/test/Integration/pass-plugins-entrypoints.f90
new file mode 100644
index 0000000000000..6bbb3421f2dc4
--- /dev/null
+++ b/flang/test/Integration/pass-plugins-entrypoints.f90
@@ -0,0 +1,64 @@
+! REQUIRES: plugins, examples
+
+! Entry-points in default and -O0 pipeline
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=full -O0 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=thin -O0 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! EP:     PipelineStart
+! EP:     PipelineEarlySimplification
+! EP-NOT: Peephole
+! EP:     ScalarOptimizerLate
+! EP-NOT: Peephole
+! EP:     OptimizerEarly
+! EP:     VectorizerStart
+! EP:     VectorizerEnd
+! EP:     OptimizerLast
+
+! Entry-points in optimizer pipeline
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-OPT %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=full \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-OPT %s
+!
+! EP-OPT: PipelineStart
+! EP-OPT: PipelineEarlySimplification
+! EP-OPT: Peephole
+! EP-OPT: ScalarOptimizerLate
+! EP-OPT: Peephole
+! EP-OPT: OptimizerEarly
+! EP-OPT: VectorizerStart
+! EP-OPT: VectorizerEnd
+! EP-OPT: OptimizerLast
+
+! FIXME: Thin-LTO does not invoke vectorizer callbacks
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=thin \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-LTO-THIN %s
+!
+! EP-LTO-THIN:     PipelineStart
+! EP-LTO-THIN:     PipelineEarlySimplification
+! EP-LTO-THIN:     Peephole
+! EP-LTO-THIN:     ScalarOptimizerLate
+! EP-LTO-THIN:     OptimizerEarly
+! EP-LTO-THIN-NOT: Vectorizer
+! EP-LTO-THIN:     OptimizerLast
+
+INTEGER FUNCTION f(x)
+  INTEGER, INTENT(IN) :: x
+  f = x
+END FUNCTION f
diff --git a/flang/test/lit.cfg.py b/flang/test/lit.cfg.py
index 4221354df34a2..1d8a1704e6838 100644
--- a/flang/test/lit.cfg.py
+++ b/flang/test/lit.cfg.py
@@ -90,8 +90,9 @@
 # directories.
 config.excludes = ["Inputs", "CMakeLists.txt", "README.txt", "LICENSE.txt"]
 
-# If the flang examples are built, add examples to the config
-if config.flang_examples:
+# Some tests depend on examples, mark them as available in the config. This
+# setting includes examples from both, flang and llvm.
+if config.build_examples:
     config.available_features.add("examples")
 
 # Plugins (loadable modules)
diff --git a/flang/test/lit.site.cfg.py.in b/flang/test/lit.site.cfg.py.in
index cc1f4fa6cc9c5..1422cc36232cb 100644
--- a/flang/test/lit.site.cfg.py.in
+++ b/flang/test/lit.site.cfg.py.in
@@ -17,7 +17,7 @@ config.flang_intrinsic_modules_dir = "@FLANG_INTRINSIC_MODULES_DIR@"
 config.flang_headers_dir = "@HEADER_BINARY_DIR@"
 config.flang_llvm_tools_dir = "@CMAKE_BINARY_DIR@/bin"
 config.flang_test_triple = "@FLANG_TEST_TARGET_TRIPLE@"
-config.flang_examples = @LLVM_BUILD_EXAMPLES@
+config.build_examples = @LLVM_BUILD_EXAMPLES@
 config.python_executable = "@PYTHON_EXECUTABLE@"
 config.flang_standalone_build = @FLANG_STANDALONE_BUILD@
 config.has_plugins = @LLVM_ENABLE_PLUGINS@
diff --git a/lld/test/ELF/lto/ltopasses-extension.ll b/lld/test/ELF/lto/ltopasses-extension.ll
index db8edbb8e087d..ce92176d07249 100644
--- a/lld/test/ELF/lto/ltopasses-extension.ll
+++ b/lld/test/ELF/lto/ltopasses-extension.ll
@@ -1,9 +1,44 @@
 ; REQUIRES: x86, plugins, examples
 ; UNSUPPORTED: target={{.*windows.*}}
-; RUN: opt -module-summary %s -o %t.o
+; RUN: opt %s -o %t.o
+; RUN: opt -module-summary %s -o %t_thin.o
+
 ; RUN: ld.lld -%loadnewpmbye --lto-newpm-passes="goodbye" -mllvm=%loadbye -mllvm=-wave-goodbye %t.o -o /dev/null 2>&1 | FileCheck %s
 ; CHECK: Bye
 
+; Entry-points in pipeline for regular/monolithic LTO
+;
+; RUN: ld.lld -%loadnewpmbye -mllvm=%loadbye -mllvm=-print-ep-callbacks %t.o \
+; RUN:         -shared -o /dev/null | FileCheck --check-prefix=REGULAR %s
+;
+; REGULAR-NOT: PipelineStart
+; REGULAR-NOT: PipelineEarlySimplification
+; REGULAR-NOT: Peephole
+; REGULAR-NOT: ScalarOptimizerLate
+; REGULAR-NOT: Vectorizer
+; REGULAR-NOT: Optimizer
+;
+; REGULAR: FullLinkTimeOptimizationEarly
+; REGULAR: FullLinkTimeOptimizationLast
+
+; Entry-points in Thin-LTO pipeline
+;
+; RUN: ld.lld -%loadnewpmbye -mllvm=%loadbye -mllvm=-print-ep-callbacks %t_thin.o \
+; RUN:         -shared -o /dev/null | FileCheck --check-prefix=THIN %s
+;
+; THIN-NOT: FullLinkTimeOptimizationEarly
+; THIN-NOT: FullLinkTimeOptimizationLast
+; THIN-NOT: PipelineStart
+;
+; THIN: PipelineEarlySimplification
+; THIN: Peephole
+; THIN: ScalarOptimizerLate
+; THIN: Peephole
+; THIN: OptimizerEarly
+; THIN: VectorizerStart
+; THIN: VectorizerEnd
+; THIN: OptimizerLast
+
 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
 target triple = "x86_64-unknown-linux-gnu"
 @junk = global i32 0
diff --git a/llvm/examples/Bye/Bye.cpp b/llvm/examples/Bye/Bye.cpp
index d88bf9e490e9c..5108cf4b3d0c0 100644
--- a/llvm/examples/Bye/Bye.cpp
+++ b/llvm/examples/Bye/Bye.cpp
@@ -11,6 +11,10 @@ using namespace llvm;
 static cl::opt<bool> Wave("wave-goodbye", cl::init(false),
                           cl::desc("wave good bye"));
 
+static cl::opt<bool> PrintEntryPointCallbacks(
+    "print-ep-callbacks", cl::init(false),
+    cl::desc("Print names of all entry-points upon callback"));
+
 namespace {
 
 bool runBye(Function &F) {
@@ -35,6 +39,102 @@ struct Bye : PassInfoMixin<Bye> {
   }
 };
 
+struct PrintStage : PassInfoMixin<Bye> {
+  PrintStage(std::string Name) : Name(std::move(Name)) {}
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
+    outs() << Name << "\n";
+    return PreservedAnalyses::none();
+  }
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
+    outs() << Name << "\n";
+    return PreservedAnalyses::none();
+  }
+
+private:
+  std::string Name;
+};
+
+void registerPassBuilderCallbacks(PassBuilder &PB) {
+  PB.registerVectorizerStartEPCallback(
+      [](llvm::FunctionPassManager &PM, OptimizationLevel Level) {
+        PM.addPass(Bye());
+      });
+  PB.registerPipelineParsingCallback(
+      [](StringRef Name, llvm::FunctionPassManager &PM,
+         ArrayRef<llvm::PassBuilder::PipelineElement>) {
+        if (Name == "goodbye") {
+          PM.addPass(Bye());
+          return true;
+        }
+        return false;
+      });
+
+  if (PrintEntryPointCallbacks) {
+    PB.registerPipelineStartEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("PipelineStart"));
+          return true;
+        });
+
+    PB.registerPipelineEarlySimplificationEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt,
+           ThinOrFullLTOPhase Phase) {
+          MPM.addPass(PrintStage("PipelineEarlySimplification"));
+          return true;
+        });
+
+    PB.registerOptimizerEarlyEPCallback([](ModulePassManager &MPM,
+                                           OptimizationLevel Opt,
+                                           ThinOrFullLTOPhase Phase) {
+      MPM.addPass(PrintStage("OptimizerEarly"));
+      return true;
+    });
+
+    PB.registerOptimizerLastEPCallback([](ModulePassManager &MPM,
+                                          OptimizationLevel Opt,
+                                          ThinOrFullLTOPhase Phase) {
+      MPM.addPass(PrintStage("OptimizerLast"));
+      return true;
+    });
+
+    PB.registerPeepholeEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("Peephole"));
+          return true;
+        });
+
+    PB.registerScalarOptimizerLateEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("ScalarOptimizerLate"));
+          return true;
+        });
+
+    PB.registerVectorizerStartEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("VectorizerStart"));
+          return true;
+        });
+
+    PB.registerVectorizerEndEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("VectorizerEnd"));
+          return true;
+        });
+
+    PB.registerFullLinkTimeOptimizationEarlyEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("FullLinkTimeOptimizationEarly"));
+          return true;
+        });
+
+    PB.registerFullLinkTimeOptimizationLastEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("FullLinkTimeOptimizationLast"));
+          return true;
+        });
+  }
+}
+
 } // namespace
 
 char LegacyBye::ID = 0;
@@ -46,21 +146,7 @@ static RegisterPass<LegacyBye> X("goodbye", "Good Bye World Pass",
 /* New PM Registration */
 llvm::PassPluginLibraryInfo getByePluginInfo() {
   return {LLVM_PLUGIN_API_VERSION, "Bye", LLVM_VERSION_STRING,
-          [](PassBuilder &PB) {
-            PB.registerVectorizerStartEPCallback(
-                [](llvm::FunctionPassManager &PM, OptimizationLevel Level) {
-                  PM.addPass(Bye());
-                });
-            PB.registerPipelineParsingCallback(
-                [](StringRef Name, llvm::FunctionPassManager &PM,
-                   ArrayRef<llvm::PassBuilder::PipelineElement>) {
-                  if (Name == "goodbye") {
-                    PM.addPass(Bye());
-                    return true;
-                  }
-                  return false;
-                });
-          }};
+          registerPassBuilderCallbacks};
 }
 
 #ifndef LLVM_BYE_LINK_INTO_TOOLS

llvmbot avatar Dec 16 '25 11:12 llvmbot

@llvm/pr-subscribers-lld

Author: Stefan Gränitz (weliveindetail)

Changes

LLVM pass-plugins are loaded into various tools. Plugins can register passes at different stages of the pipeline, called entry-points. Depending on the pipeline's configuration, different entry-points will be exercised. AFAIK there is no documentation on which entry-points are supposed to be exercised under which conditions. This patch adds regression tests for the status quo in the major consumers of pass-plugins.


Full diff: https://github.com/llvm/llvm-project/pull/172463.diff

10 Files Affected:

  • (modified) clang/test/CMakeLists.txt (+6)
  • (added) clang/test/CodeGen/pass-plugins-entrypoints.c (+66)
  • (added) clang/test/Interpreter/pass-plugins.cpp (+9)
  • (modified) clang/test/lit.cfg.py (+2)
  • (modified) clang/test/lit.site.cfg.py.in (+1)
  • (added) flang/test/Integration/pass-plugins-entrypoints.f90 (+64)
  • (modified) flang/test/lit.cfg.py (+3-2)
  • (modified) flang/test/lit.site.cfg.py.in (+1-1)
  • (modified) lld/test/ELF/lto/ltopasses-extension.ll (+36-1)
  • (modified) llvm/examples/Bye/Bye.cpp (+101-15)
diff --git a/clang/test/CMakeLists.txt b/clang/test/CMakeLists.txt
index bcb6bd68fafc2..05b0ee42da42b 100644
--- a/clang/test/CMakeLists.txt
+++ b/clang/test/CMakeLists.txt
@@ -134,6 +134,12 @@ if(CLANG_BUILD_EXAMPLES AND CLANG_PLUGIN_SUPPORT)
     )
 endif ()
 
+if(LLVM_BUILD_EXAMPLES AND NOT WIN32)
+  list(APPEND CLANG_TEST_DEPS
+    Bye
+    )
+endif()
+
 if(LLVM_INCLUDE_SPIRV_TOOLS_TESTS)
   list(APPEND CLANG_TEST_DEPS
     spirv-dis
diff --git a/clang/test/CodeGen/pass-plugins-entrypoints.c b/clang/test/CodeGen/pass-plugins-entrypoints.c
new file mode 100644
index 0000000000000..90182961b8057
--- /dev/null
+++ b/clang/test/CodeGen/pass-plugins-entrypoints.c
@@ -0,0 +1,66 @@
+// REQUIRES: plugins, llvm-examples
+
+// Entry-points in default and -O0 pipeline
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=full -O0 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=thin -O0 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP %s
+//
+// EP:     PipelineStart
+// EP:     PipelineEarlySimplification
+// EP-NOT: Peephole
+// EP-NOT: ScalarOptimizerLate
+// EP-NOT: Peephole
+// EP:     OptimizerEarly
+// EP-NOT: Vectorizer
+// EP:     OptimizerLast
+
+// Entry-points in optimizer pipeline
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=full \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -ffat-lto-objects \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-OPT %s
+//
+// EP-OPT: PipelineStart
+// EP-OPT: PipelineEarlySimplification
+// EP-OPT: Peephole
+// EP-OPT: ScalarOptimizerLate
+// EP-OPT: Peephole
+// EP-OPT: OptimizerEarly
+// EP-OPT: VectorizerStart
+// EP-OPT: VectorizerEnd
+// EP-OPT: OptimizerLast
+
+// FIXME: Thin-LTO does not invoke vectorizer callbacks
+//
+// RUN: %clang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=thin \
+// RUN:        -Xclang -load -Xclang %llvmshlibdir/Bye%pluginext \
+// RUN:        -mllvm -print-ep-callbacks -o /dev/null -S -emit-llvm %s | FileCheck --check-prefix=EP-LTO-THIN %s
+//
+// EP-LTO-THIN:     PipelineStart
+// EP-LTO-THIN:     PipelineEarlySimplification
+// EP-LTO-THIN:     Peephole
+// EP-LTO-THIN:     ScalarOptimizerLate
+// EP-LTO-THIN:     OptimizerEarly
+// EP-LTO-THIN-NOT: Vectorizer
+// EP-LTO-THIN:     OptimizerLast
+
+int f(int x) {
+  return x;
+}
diff --git a/clang/test/Interpreter/pass-plugins.cpp b/clang/test/Interpreter/pass-plugins.cpp
new file mode 100644
index 0000000000000..4d445807c3d70
--- /dev/null
+++ b/clang/test/Interpreter/pass-plugins.cpp
@@ -0,0 +1,9 @@
+// RUN: cat %s | clang-repl -Xcc -fpass-plugin=%plugindir/pypass-plugin%pluginext \
+// RUN:                     -Xcc -load=%plugindir/pypass-plugin%pluginext \
+// RUN:                     -Xcc -Xclang -Xcc -mllvm -Xcc -wave-goodbye | FileCheck %s
+// REQUIRES: plugins, llvm-examples
+
+int i = 10;
+%quit
+
+// CHECK: Bye
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index 52b275c095475..a622f5335354a 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -126,6 +126,8 @@
 
 if config.clang_examples:
     config.available_features.add("examples")
+if config.llvm_examples:
+    config.available_features.add("llvm-examples")
 
 
 def have_host_out_of_process_jit_feature_support():
diff --git a/clang/test/lit.site.cfg.py.in b/clang/test/lit.site.cfg.py.in
index f50953a93a412..8e0ecdbe07805 100644
--- a/clang/test/lit.site.cfg.py.in
+++ b/clang/test/lit.site.cfg.py.in
@@ -28,6 +28,7 @@ config.clang_staticanalyzer_z3 = @LLVM_WITH_Z3@
 config.clang_staticanalyzer_z3_mock = @TEST_WITH_Z3_MOCK@
 config.clang_enable_cir = @CLANG_ENABLE_CIR@
 config.clang_examples = @CLANG_BUILD_EXAMPLES@
+config.llvm_examples = @LLVM_BUILD_EXAMPLES@
 config.enable_shared = @ENABLE_SHARED@
 config.enable_backtrace = @ENABLE_BACKTRACES@
 config.enable_threads = @LLVM_ENABLE_THREADS@
diff --git a/flang/test/Integration/pass-plugins-entrypoints.f90 b/flang/test/Integration/pass-plugins-entrypoints.f90
new file mode 100644
index 0000000000000..6bbb3421f2dc4
--- /dev/null
+++ b/flang/test/Integration/pass-plugins-entrypoints.f90
@@ -0,0 +1,64 @@
+! REQUIRES: plugins, examples
+
+! Entry-points in default and -O0 pipeline
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=full -O0 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -flto=thin -O0 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP %s
+!
+! EP:     PipelineStart
+! EP:     PipelineEarlySimplification
+! EP-NOT: Peephole
+! EP:     ScalarOptimizerLate
+! EP-NOT: Peephole
+! EP:     OptimizerEarly
+! EP:     VectorizerStart
+! EP:     VectorizerEnd
+! EP:     OptimizerLast
+
+! Entry-points in optimizer pipeline
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-OPT %s
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=full \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-OPT %s
+!
+! EP-OPT: PipelineStart
+! EP-OPT: PipelineEarlySimplification
+! EP-OPT: Peephole
+! EP-OPT: ScalarOptimizerLate
+! EP-OPT: Peephole
+! EP-OPT: OptimizerEarly
+! EP-OPT: VectorizerStart
+! EP-OPT: VectorizerEnd
+! EP-OPT: OptimizerLast
+
+! FIXME: Thin-LTO does not invoke vectorizer callbacks
+!
+! RUN: %flang -fpass-plugin=%llvmshlibdir/Bye%pluginext -O2 -flto=thin \
+! RUN:        -Xflang -load -Xflang %llvmshlibdir/Bye%pluginext \
+! RUN:        -mllvm -print-ep-callbacks -o /dev/null -S %s | FileCheck --check-prefix=EP-LTO-THIN %s
+!
+! EP-LTO-THIN:     PipelineStart
+! EP-LTO-THIN:     PipelineEarlySimplification
+! EP-LTO-THIN:     Peephole
+! EP-LTO-THIN:     ScalarOptimizerLate
+! EP-LTO-THIN:     OptimizerEarly
+! EP-LTO-THIN-NOT: Vectorizer
+! EP-LTO-THIN:     OptimizerLast
+
+INTEGER FUNCTION f(x)
+  INTEGER, INTENT(IN) :: x
+  f = x
+END FUNCTION f
diff --git a/flang/test/lit.cfg.py b/flang/test/lit.cfg.py
index 4221354df34a2..1d8a1704e6838 100644
--- a/flang/test/lit.cfg.py
+++ b/flang/test/lit.cfg.py
@@ -90,8 +90,9 @@
 # directories.
 config.excludes = ["Inputs", "CMakeLists.txt", "README.txt", "LICENSE.txt"]
 
-# If the flang examples are built, add examples to the config
-if config.flang_examples:
+# Some tests depend on examples, mark them as available in the config. This
+# setting includes examples from both, flang and llvm.
+if config.build_examples:
     config.available_features.add("examples")
 
 # Plugins (loadable modules)
diff --git a/flang/test/lit.site.cfg.py.in b/flang/test/lit.site.cfg.py.in
index cc1f4fa6cc9c5..1422cc36232cb 100644
--- a/flang/test/lit.site.cfg.py.in
+++ b/flang/test/lit.site.cfg.py.in
@@ -17,7 +17,7 @@ config.flang_intrinsic_modules_dir = "@FLANG_INTRINSIC_MODULES_DIR@"
 config.flang_headers_dir = "@HEADER_BINARY_DIR@"
 config.flang_llvm_tools_dir = "@CMAKE_BINARY_DIR@/bin"
 config.flang_test_triple = "@FLANG_TEST_TARGET_TRIPLE@"
-config.flang_examples = @LLVM_BUILD_EXAMPLES@
+config.build_examples = @LLVM_BUILD_EXAMPLES@
 config.python_executable = "@PYTHON_EXECUTABLE@"
 config.flang_standalone_build = @FLANG_STANDALONE_BUILD@
 config.has_plugins = @LLVM_ENABLE_PLUGINS@
diff --git a/lld/test/ELF/lto/ltopasses-extension.ll b/lld/test/ELF/lto/ltopasses-extension.ll
index db8edbb8e087d..ce92176d07249 100644
--- a/lld/test/ELF/lto/ltopasses-extension.ll
+++ b/lld/test/ELF/lto/ltopasses-extension.ll
@@ -1,9 +1,44 @@
 ; REQUIRES: x86, plugins, examples
 ; UNSUPPORTED: target={{.*windows.*}}
-; RUN: opt -module-summary %s -o %t.o
+; RUN: opt %s -o %t.o
+; RUN: opt -module-summary %s -o %t_thin.o
+
 ; RUN: ld.lld -%loadnewpmbye --lto-newpm-passes="goodbye" -mllvm=%loadbye -mllvm=-wave-goodbye %t.o -o /dev/null 2>&1 | FileCheck %s
 ; CHECK: Bye
 
+; Entry-points in pipeline for regular/monolithic LTO
+;
+; RUN: ld.lld -%loadnewpmbye -mllvm=%loadbye -mllvm=-print-ep-callbacks %t.o \
+; RUN:         -shared -o /dev/null | FileCheck --check-prefix=REGULAR %s
+;
+; REGULAR-NOT: PipelineStart
+; REGULAR-NOT: PipelineEarlySimplification
+; REGULAR-NOT: Peephole
+; REGULAR-NOT: ScalarOptimizerLate
+; REGULAR-NOT: Vectorizer
+; REGULAR-NOT: Optimizer
+;
+; REGULAR: FullLinkTimeOptimizationEarly
+; REGULAR: FullLinkTimeOptimizationLast
+
+; Entry-points in Thin-LTO pipeline
+;
+; RUN: ld.lld -%loadnewpmbye -mllvm=%loadbye -mllvm=-print-ep-callbacks %t_thin.o \
+; RUN:         -shared -o /dev/null | FileCheck --check-prefix=THIN %s
+;
+; THIN-NOT: FullLinkTimeOptimizationEarly
+; THIN-NOT: FullLinkTimeOptimizationLast
+; THIN-NOT: PipelineStart
+;
+; THIN: PipelineEarlySimplification
+; THIN: Peephole
+; THIN: ScalarOptimizerLate
+; THIN: Peephole
+; THIN: OptimizerEarly
+; THIN: VectorizerStart
+; THIN: VectorizerEnd
+; THIN: OptimizerLast
+
 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
 target triple = "x86_64-unknown-linux-gnu"
 @junk = global i32 0
diff --git a/llvm/examples/Bye/Bye.cpp b/llvm/examples/Bye/Bye.cpp
index d88bf9e490e9c..5108cf4b3d0c0 100644
--- a/llvm/examples/Bye/Bye.cpp
+++ b/llvm/examples/Bye/Bye.cpp
@@ -11,6 +11,10 @@ using namespace llvm;
 static cl::opt<bool> Wave("wave-goodbye", cl::init(false),
                           cl::desc("wave good bye"));
 
+static cl::opt<bool> PrintEntryPointCallbacks(
+    "print-ep-callbacks", cl::init(false),
+    cl::desc("Print names of all entry-points upon callback"));
+
 namespace {
 
 bool runBye(Function &F) {
@@ -35,6 +39,102 @@ struct Bye : PassInfoMixin<Bye> {
   }
 };
 
+struct PrintStage : PassInfoMixin<Bye> {
+  PrintStage(std::string Name) : Name(std::move(Name)) {}
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &) {
+    outs() << Name << "\n";
+    return PreservedAnalyses::none();
+  }
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
+    outs() << Name << "\n";
+    return PreservedAnalyses::none();
+  }
+
+private:
+  std::string Name;
+};
+
+void registerPassBuilderCallbacks(PassBuilder &PB) {
+  PB.registerVectorizerStartEPCallback(
+      [](llvm::FunctionPassManager &PM, OptimizationLevel Level) {
+        PM.addPass(Bye());
+      });
+  PB.registerPipelineParsingCallback(
+      [](StringRef Name, llvm::FunctionPassManager &PM,
+         ArrayRef<llvm::PassBuilder::PipelineElement>) {
+        if (Name == "goodbye") {
+          PM.addPass(Bye());
+          return true;
+        }
+        return false;
+      });
+
+  if (PrintEntryPointCallbacks) {
+    PB.registerPipelineStartEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("PipelineStart"));
+          return true;
+        });
+
+    PB.registerPipelineEarlySimplificationEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt,
+           ThinOrFullLTOPhase Phase) {
+          MPM.addPass(PrintStage("PipelineEarlySimplification"));
+          return true;
+        });
+
+    PB.registerOptimizerEarlyEPCallback([](ModulePassManager &MPM,
+                                           OptimizationLevel Opt,
+                                           ThinOrFullLTOPhase Phase) {
+      MPM.addPass(PrintStage("OptimizerEarly"));
+      return true;
+    });
+
+    PB.registerOptimizerLastEPCallback([](ModulePassManager &MPM,
+                                          OptimizationLevel Opt,
+                                          ThinOrFullLTOPhase Phase) {
+      MPM.addPass(PrintStage("OptimizerLast"));
+      return true;
+    });
+
+    PB.registerPeepholeEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("Peephole"));
+          return true;
+        });
+
+    PB.registerScalarOptimizerLateEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("ScalarOptimizerLate"));
+          return true;
+        });
+
+    PB.registerVectorizerStartEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("VectorizerStart"));
+          return true;
+        });
+
+    PB.registerVectorizerEndEPCallback(
+        [](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          FPM.addPass(PrintStage("VectorizerEnd"));
+          return true;
+        });
+
+    PB.registerFullLinkTimeOptimizationEarlyEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("FullLinkTimeOptimizationEarly"));
+          return true;
+        });
+
+    PB.registerFullLinkTimeOptimizationLastEPCallback(
+        [](ModulePassManager &MPM, OptimizationLevel Opt) {
+          MPM.addPass(PrintStage("FullLinkTimeOptimizationLast"));
+          return true;
+        });
+  }
+}
+
 } // namespace
 
 char LegacyBye::ID = 0;
@@ -46,21 +146,7 @@ static RegisterPass<LegacyBye> X("goodbye", "Good Bye World Pass",
 /* New PM Registration */
 llvm::PassPluginLibraryInfo getByePluginInfo() {
   return {LLVM_PLUGIN_API_VERSION, "Bye", LLVM_VERSION_STRING,
-          [](PassBuilder &PB) {
-            PB.registerVectorizerStartEPCallback(
-                [](llvm::FunctionPassManager &PM, OptimizationLevel Level) {
-                  PM.addPass(Bye());
-                });
-            PB.registerPipelineParsingCallback(
-                [](StringRef Name, llvm::FunctionPassManager &PM,
-                   ArrayRef<llvm::PassBuilder::PipelineElement>) {
-                  if (Name == "goodbye") {
-                    PM.addPass(Bye());
-                    return true;
-                  }
-                  return false;
-                });
-          }};
+          registerPassBuilderCallbacks};
 }
 
 #ifndef LLVM_BYE_LINK_INTO_TOOLS

llvmbot avatar Dec 16 '25 11:12 llvmbot

By the way, an alternative testing method of which callbacks are invoked by which pipeline can be seen in llvm/test/Other/new-pm-O0-ep-callbacks.ll. That does not rely on plugins at all.

nikic avatar Dec 16 '25 11:12 nikic

:penguin: Linux x64 Test Results

  • 194434 tests passed
  • 6299 tests skipped

:white_check_mark: The build succeeded and all tests passed.

github-actions[bot] avatar Dec 16 '25 12:12 github-actions[bot]

I'd rather not have these tests duplicated across different tools in different projects. Changing pass builder callbacks should not require changes to flang tests.

I think tests that the plugin mechanism is working makes sense for all tools. A lot can wrong in each tool, from not parsing the command line arguments to forgetting to call the plugin handling mechnism. In short: it should be tested, at least a smoke test.

Meinersbur avatar Dec 16 '25 12:12 Meinersbur

Shouldn't testing these via opt be sufficient? [...] As to which callbacks should get invoked, yeah, that's somewhat messy.

It would be great to have transparency, what entry-points are available across tools with their individual configurations. It's anything than obvious which of the LTO pipeline builder is used in which configuration of a tool. Tests are a good way to document that behavior and make sure it's up-to-date.

It looks like some of the callbacks are also missing here, e.g. the loop ones?

Not all callbacks are registered (yet). The ones that are, also get tested. Happy to add others on request.

Would it be better to have the PrintStage pass be part of its own pass plugin?

No strong opinion here. Happy to go for what seems best for everyone. The reason for extending Bye is that it is already used for testing across projects.

weliveindetail avatar Dec 16 '25 12:12 weliveindetail

I think tests that the plugin mechanism is working makes sense for all tools.

I agree, but I also agree with nikic that it should only be a smoke test per tool. The extension points could be just tested with opt.

That said, could we wait with Clang plugin changes until #171868 is merged? For Clang, this PR is currently just duplicating efforts.

aengelke avatar Dec 16 '25 12:12 aengelke

I think tests that the plugin mechanism is working makes sense for all tools.

I agree, but I also agree with nikic that it should only be a smoke test per tool. The extension points could be just tested with opt.

That said, could we wait with Clang plugin changes until #171868 is merged? For Clang, this PR is currently just duplicating efforts.

Right. To be clear, I do think we should have basic tests for the plugin mechanism in all tools, along the lines of what https://github.com/llvm/llvm-project/pull/171868 does. I'm only objecting to testing of pass pipeline hooks being spread across lots of different projects.

nikic avatar Dec 16 '25 13:12 nikic

Feedback so far is, that these tests aren't necessary. I think they are.

There is value in documenting plugin entry-points for tools in their individual configurations. It makes sense to do this in tests in a readable way. We must fix them if the behavior changes and we can add entry-points when we introduce new ones. It would be easy to check which version of which tools supported which set of entry-points.

Yes, there are tests for the individual pipelines in LLVM. These are tests for core features, they are hard to read and they give no overview. I can imagine that this is all obvious for full-time LLVM experts, but it's not clear for plugin developers who use LLVM as one tool out of many. I think we should provide better transparency and this patch could help.

Let me leave the PR here for documentation for now. And maybe as a reference for people who start working with pass plugins in the future.

weliveindetail avatar Dec 16 '25 15:12 weliveindetail

FYI @jeanPerier @eugeneepshteyn @vzakhari

ashermancinelli avatar Dec 16 '25 22:12 ashermancinelli