https://github.com/weliveindetail created 
https://github.com/llvm/llvm-project/pull/171101

None

From cc6774c13f4400208c452b5b14e557b6deee8c1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= <[email protected]>
Date: Tue, 2 Dec 2025 18:37:07 +0100
Subject: [PATCH] [RFC] Initial reference pass-plugin in LLVM

---
 clang/test/Misc/Inputs/pypass-plugin.c        |   2 +
 clang/test/Misc/lit.local.cfg                 |  19 +
 clang/test/Misc/pypass-plugin-entrypoints.py  | 116 +++++
 clang/test/Misc/pypass-plugin-params.py       |  28 ++
 clang/test/Misc/pypass-plugin-repl.py         |  15 +
 clang/test/lit.cfg.py                         |   3 +
 lld/test/CMakeLists.txt                       |   1 +
 lld/test/ELF/lto/Inputs/pypass-plugin.ll      |   7 +
 lld/test/ELF/lto/lit.local.cfg                |  21 +
 lld/test/ELF/lto/pypass-plugin-entrypoints.py |  89 ++++
 .../ELF/lto/pypass-plugin-pipeline-parsing.py |  16 +
 lld/test/lit.cfg.py                           |   3 +
 lld/test/lit.site.cfg.py.in                   |   1 +
 llvm/CMakeLists.txt                           |   4 +
 llvm/cmake/modules/AddLLVM.cmake              |  22 +-
 llvm/docs/CMake.rst                           |   5 +
 .../test/tools/plugins-shlib/Inputs/foobar.ll |   2 +
 llvm/test/tools/plugins-shlib/Inputs/mymod.py |   4 +
 llvm/test/tools/plugins-shlib/lit.local.cfg   |  19 +
 .../tools/plugins-shlib/pypass-functions.py   |  20 +
 llvm/test/tools/plugins-shlib/pypass.py       |  34 ++
 llvm/tools/plugins-shlib/CMakeLists.txt       |  16 +
 llvm/tools/plugins-shlib/pypass.cpp           | 463 ++++++++++++++++++
 23 files changed, 902 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/Misc/Inputs/pypass-plugin.c
 create mode 100644 clang/test/Misc/lit.local.cfg
 create mode 100644 clang/test/Misc/pypass-plugin-entrypoints.py
 create mode 100644 clang/test/Misc/pypass-plugin-params.py
 create mode 100644 clang/test/Misc/pypass-plugin-repl.py
 create mode 100644 lld/test/ELF/lto/Inputs/pypass-plugin.ll
 create mode 100644 lld/test/ELF/lto/lit.local.cfg
 create mode 100644 lld/test/ELF/lto/pypass-plugin-entrypoints.py
 create mode 100644 lld/test/ELF/lto/pypass-plugin-pipeline-parsing.py
 create mode 100644 llvm/test/tools/plugins-shlib/Inputs/foobar.ll
 create mode 100644 llvm/test/tools/plugins-shlib/Inputs/mymod.py
 create mode 100644 llvm/test/tools/plugins-shlib/lit.local.cfg
 create mode 100644 llvm/test/tools/plugins-shlib/pypass-functions.py
 create mode 100644 llvm/test/tools/plugins-shlib/pypass.py
 create mode 100644 llvm/tools/plugins-shlib/CMakeLists.txt
 create mode 100644 llvm/tools/plugins-shlib/pypass.cpp

diff --git a/clang/test/Misc/Inputs/pypass-plugin.c 
b/clang/test/Misc/Inputs/pypass-plugin.c
new file mode 100644
index 0000000000000..90c45543bf62c
--- /dev/null
+++ b/clang/test/Misc/Inputs/pypass-plugin.c
@@ -0,0 +1,2 @@
+void f() {}
+void g() {}
diff --git a/clang/test/Misc/lit.local.cfg b/clang/test/Misc/lit.local.cfg
new file mode 100644
index 0000000000000..e1f14c36caa3d
--- /dev/null
+++ b/clang/test/Misc/lit.local.cfg
@@ -0,0 +1,19 @@
+import os
+import sysconfig
+
+# Run end-to-end tests if the reference pass-plugin exists in LLVM
+pypass_plugin = f"pypass-plugin{config.llvm_plugin_ext}"
+pypass_plugin_shlib = os.path.join(config.llvm_libs_dir, pypass_plugin)
+if os.path.exists(pypass_plugin_shlib):
+    config.available_features.add("pypass-plugin")
+
+# Disable ASAN's leak detection for Python tests
+config.environment["ASAN_OPTIONS"] = "detect_leaks=0"
+
+libdir = sysconfig.get_config_var("LIBDIR")
+dylib = sysconfig.get_config_var("LDLIBRARY")
+config.substitutions.append(("%libpython", os.path.join(libdir, dylib)))
+
+# FIXME: %llvmshlibdir is broken in standalone builds
+config.substitutions.append(("%plugindir", config.llvm_libs_dir))
+config.suffixes.add(".py")
diff --git a/clang/test/Misc/pypass-plugin-entrypoints.py 
b/clang/test/Misc/pypass-plugin-entrypoints.py
new file mode 100644
index 0000000000000..a5b299c05380c
--- /dev/null
+++ b/clang/test/Misc/pypass-plugin-entrypoints.py
@@ -0,0 +1,116 @@
+# REQUIRES: native, system-linux, plugins, pypass-plugin
+
+# Entry-points in default and -O0 pipeline
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP %s
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -flto=full 
-O0 \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP %s
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -flto=thin 
-O0 \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP %s
+#
+# EP-NOT: PeepholeEPCallback
+# EP-NOT: Optimizer{{.*}}EPCallback
+# EP-NOT: ScalarOptimizer{{.*}}EPCallback
+# EP-NOT: FullLinkTimeOptimization{{.*}}EPCallback
+#
+# EP: PipelineStartEPCallback
+# EP: PipelineEarlySimplificationEPCallback
+# EP: OptimizerEarlyEPCallback
+# EP: OptimizerLastEPCallback
+
+# Entry-points in optimizer pipeline
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -O2 \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP-OPT %s
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -O2 
-flto=full \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP-OPT %s
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -O2 
-ffat-lto-objects \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP-OPT %s
+#
+# EP-OPT:     PipelineStartEPCallback
+# EP-OPT:     PipelineEarlySimplificationEPCallback
+# EP-OPT:     PeepholeEPCallback
+# EP-OPT:     ScalarOptimizerLateEPCallback
+# EP-OPT:     PeepholeEPCallback
+# EP-OPT:     OptimizerEarlyEPCallback
+# EP-OPT:     VectorizerStartEPCallback
+# EP-OPT:     VectorizerEndEPCallback
+# EP-OPT:     OptimizerLastEPCallback
+
+# FIXME: Thin-LTO does not invoke vectorizer callbacks
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -O2 
-flto=thin \
+# RUN:          -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c | 
FileCheck --check-prefix=EP-LTO-THIN %s
+#
+# EP-LTO-THIN:     PipelineStartEPCallback
+# EP-LTO-THIN:     PipelineEarlySimplificationEPCallback
+# EP-LTO-THIN:     PeepholeEPCallback
+# EP-LTO-THIN:     ScalarOptimizerLateEPCallback
+# EP-LTO-THIN:     OptimizerEarlyEPCallback
+# EP-LTO-THIN-NOT: Vectorizer{{.*}}EPCallback
+# EP-LTO-THIN:     OptimizerLastEPCallback
+
+def registerPipelineStartEPCallback():
+    """ Module pass at the start of the pipeline """
+    return True
+
+def registerPipelineEarlySimplificationEPCallback():
+    """ Module pass after basic simplification of input IR """
+    return True
+
+def registerOptimizerEarlyEPCallback():
+    """ Module pass before the function optimization pipeline """
+    return True
+
+def registerOptimizerLastEPCallback():
+    """ Module pass after the function optimization pipeline """
+    return True
+
+def registerPeepholeEPCallback():
+    """ Function pass after each instance of the instruction combiner pass """
+    return True
+
+def registerScalarOptimizerLateEPCallback():
+    """ Function pass after most of the main optimizations, but before the last
+        cleanup-ish optimizations"""
+    return True
+
+def registerVectorizerStartEPCallback():
+    """ Function pass before the vectorizer and other highly target specific
+        optimization passes are executed. """
+    return True
+
+def registerVectorizerEndEPCallback():
+    """ Function pass after the vectorizer and other highly target specific
+        optimization passes are executed. """
+    return True
+
+def registerFullLinkTimeOptimizationEarlyEPCallback():
+    """ Module pass at the start of the full LTO pipeline """
+    return True
+
+def registerFullLinkTimeOptimizationLastEPCallback():
+    """ Module pass at the end of the full LTO pipeline """
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/clang/test/Misc/pypass-plugin-params.py 
b/clang/test/Misc/pypass-plugin-params.py
new file mode 100644
index 0000000000000..f0c54eab1f2e8
--- /dev/null
+++ b/clang/test/Misc/pypass-plugin-params.py
@@ -0,0 +1,28 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+
+# XFAIL: *
+#
+# RUN: %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -S -emit-llvm \
+# RUN:        -mllvm -pypass-script=%s \
+# RUN:        -mllvm -pypass-dylib=%libpython \
+# RUN:        -Xclang -fdebug-pass-manager \
+# RUN:        -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c 2>&1 | 
FileCheck %s
+#
+# CHECK: Unknown command line argument
+
+# Plugin parameters only work with the extra `-Xclang -load -Xclang <path>`
+# RUN: %clang -fpass-plugin=%plugindir/pypass-plugin%pluginext -S -emit-llvm \
+# RUN:        -Xclang -load -Xclang %plugindir/pypass-plugin%pluginext \
+# RUN:        -mllvm -pypass-script=%s \
+# RUN:        -mllvm -pypass-dylib=%libpython \
+# RUN:        -Xclang -fdebug-pass-manager \
+# RUN:        -o /dev/null -S -emit-llvm %S/Inputs/pypass-plugin.c 2>&1 | 
FileCheck %s
+#
+# CHECK: Running pass: PyPass
+
+def registerPipelineEarlySimplificationEPCallback():
+    """ Module pass after basic simplification of input IR """
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/clang/test/Misc/pypass-plugin-repl.py 
b/clang/test/Misc/pypass-plugin-repl.py
new file mode 100644
index 0000000000000..38fbfc3961a5c
--- /dev/null
+++ b/clang/test/Misc/pypass-plugin-repl.py
@@ -0,0 +1,15 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+
+# RUN: echo "int a = 1;" | \
+# RUN:   env LLVM_PYPASS_SCRIPT=%s \
+# RUN:   env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:     clang-repl -Xcc -fpass-plugin=%plugindir/pypass-plugin%pluginext | 
FileCheck %s
+#
+# CHECK: PipelineEarlySimplificationEPCallback
+
+def registerPipelineEarlySimplificationEPCallback():
+    """ Module pass after basic simplification of input IR """
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/clang/test/lit.cfg.py b/clang/test/lit.cfg.py
index 52b275c095475..c3d070e566042 100644
--- a/clang/test/lit.cfg.py
+++ b/clang/test/lit.cfg.py
@@ -279,6 +279,9 @@ def have_host_clang_repl_cuda():
 if not (config.build_shared_libs or config.link_llvm_dylib or 
config.link_clang_dylib):
     config.available_features.add("static-libs")
 
+if config.link_llvm_dylib:
+    config.available_features.add("llvm-dylib")
+
 # Plugins (loadable modules)
 if config.has_plugins and config.llvm_plugin_ext:
     config.available_features.add("plugins")
diff --git a/lld/test/CMakeLists.txt b/lld/test/CMakeLists.txt
index 1bd3ad7e1765b..e19bccae38fc5 100644
--- a/lld/test/CMakeLists.txt
+++ b/lld/test/CMakeLists.txt
@@ -5,6 +5,7 @@ llvm_canonicalize_cmake_booleans(
   LLVM_ENABLE_ZSTD
   LLVM_ENABLE_LIBXML2
   LLD_DEFAULT_LD_LLD_IS_MINGW
+  LLVM_LINK_LLVM_DYLIB
   LLVM_BUILD_EXAMPLES
   LLVM_ENABLE_PLUGINS
   LLVM_BYE_LINK_INTO_TOOLS
diff --git a/lld/test/ELF/lto/Inputs/pypass-plugin.ll 
b/lld/test/ELF/lto/Inputs/pypass-plugin.ll
new file mode 100644
index 0000000000000..c73e881a3e38d
--- /dev/null
+++ b/lld/test/ELF/lto/Inputs/pypass-plugin.ll
@@ -0,0 +1,7 @@
+target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i32 @main() {
+entry:
+  ret i32 0
+}
diff --git a/lld/test/ELF/lto/lit.local.cfg b/lld/test/ELF/lto/lit.local.cfg
new file mode 100644
index 0000000000000..c6837f99868aa
--- /dev/null
+++ b/lld/test/ELF/lto/lit.local.cfg
@@ -0,0 +1,21 @@
+import os
+import sysconfig
+
+pypass_plugin = f"pypass-plugin{config.llvm_shlib_ext}"
+pypass_plugin_shlib = os.path.join(config.llvm_shlib_dir, pypass_plugin)
+
+# Run end-to-end tests if the reference pass-plugin exists in LLVM
+if os.path.exists(pypass_plugin_shlib):
+    config.available_features.add("pypass-plugin")
+
+# Disable ASAN's leak detection for Python tests
+config.environment["ASAN_OPTIONS"] = "detect_leaks=0"
+
+libdir = sysconfig.get_config_var("LIBDIR")
+dylib = sysconfig.get_config_var("LDLIBRARY")
+config.substitutions.append(("%libpython", os.path.join(libdir, dylib)))
+
+# FIXME: %llvmshlibdir is broken in standalone builds
+config.substitutions.append(("%plugindir", config.llvm_libs_dir))
+config.substitutions.append(("%pluginext", config.llvm_shlib_ext))
+config.suffixes.add(".py")
diff --git a/lld/test/ELF/lto/pypass-plugin-entrypoints.py 
b/lld/test/ELF/lto/pypass-plugin-entrypoints.py
new file mode 100644
index 0000000000000..cc3a57a2ac18e
--- /dev/null
+++ b/lld/test/ELF/lto/pypass-plugin-entrypoints.py
@@ -0,0 +1,89 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+
+# Entry-points in pipeline for regular/monolithic LTO
+#
+# RUN: opt %S/Inputs/pypass-plugin.ll -o %t.o
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   ld.lld --load-pass-plugin=%plugindir/pypass-plugin%pluginext %t.o \
+# RUN:           -shared -o /dev/null | FileCheck --check-prefix=REGULAR %s
+#
+# REGULAR-NOT: PipelineStartEPCallback
+# REGULAR-NOT: PipelineEarlySimplificationEPCallback
+# REGULAR-NOT: PeepholeEPCallback
+# REGULAR-NOT: ScalarOptimizerLateEPCallback
+# REGULAR-NOT: Vectorizer{{.*}}EPCallback
+# REGULAR-NOT: Optimizer{{.*}}EPCallback
+#
+# REGULAR: FullLinkTimeOptimizationEarlyEPCallback
+# REGULAR: FullLinkTimeOptimizationLastEPCallback
+
+# Entry-points in Thin-LTO pipeline
+#
+# RUN: opt --thinlto-bc %S/Inputs/pypass-plugin.ll -o %t_thin1.o
+# RUN: opt -module-summary %S/Inputs/pypass-plugin.ll -o %t_thin2.bc
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   ld.lld --load-pass-plugin=%plugindir/pypass-plugin%pluginext 
%t_thin2.bc \
+# RUN:           -shared -o /dev/null | FileCheck --check-prefix=THIN %s
+#
+# THIN-NOT: FullLinkTimeOptimizationEarlyEPCallback
+# THIN-NOT: FullLinkTimeOptimizationLastEPCallback
+# THIN-NOT: PipelineStartEPCallback
+#
+# THIN: PipelineEarlySimplificationEPCallback
+# THIN: PeepholeEPCallback
+# THIN: ScalarOptimizerLateEPCallback
+# THIN: PeepholeEPCallback
+# THIN: OptimizerEarlyEPCallback
+# THIN: VectorizerStartEPCallback
+# THIN: VectorizerEndEPCallback
+# THIN: OptimizerLastEPCallback
+
+def registerPipelineStartEPCallback():
+    """ Module pass at the start of the pipeline """
+    return True
+
+def registerPipelineEarlySimplificationEPCallback():
+    """ Module pass after basic simplification of input IR """
+    return True
+
+def registerOptimizerEarlyEPCallback():
+    """ Module pass before the function optimization pipeline """
+    return True
+
+def registerOptimizerLastEPCallback():
+    """ Module pass after the function optimization pipeline """
+    return True
+
+def registerPeepholeEPCallback():
+    """ Function pass after each instance of the instruction combiner pass """
+    return True
+
+def registerScalarOptimizerLateEPCallback():
+    """ Function pass after most of the main optimizations, but before the last
+        cleanup-ish optimizations"""
+    return True
+
+def registerVectorizerStartEPCallback():
+    """ Function pass before the vectorizer and other highly target specific
+        optimization passes are executed. """
+    return True
+
+def registerVectorizerEndEPCallback():
+    """ Function pass after the vectorizer and other highly target specific
+        optimization passes are executed. """
+    return True
+
+def registerFullLinkTimeOptimizationEarlyEPCallback():
+    """ Module pass at the start of the full LTO pipeline """
+    return True
+
+def registerFullLinkTimeOptimizationLastEPCallback():
+    """ Module pass at the end of the full LTO pipeline """
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/lld/test/ELF/lto/pypass-plugin-pipeline-parsing.py 
b/lld/test/ELF/lto/pypass-plugin-pipeline-parsing.py
new file mode 100644
index 0000000000000..49195daa75fd6
--- /dev/null
+++ b/lld/test/ELF/lto/pypass-plugin-pipeline-parsing.py
@@ -0,0 +1,16 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+#
+# RUN: opt %S/Inputs/pypass-plugin.ll -o %t.o
+#
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN:   ld.lld --load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:          --lto-newpm-passes=pypass %t.o -o /dev/null | FileCheck %s
+#
+# CHECK: 0x{{[0-9a-f]+}} Module
+
+def registerModulePipelineParsingCallback():
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/lld/test/lit.cfg.py b/lld/test/lit.cfg.py
index 39c3d0aa36bfb..162672b3fc157 100644
--- a/lld/test/lit.cfg.py
+++ b/lld/test/lit.cfg.py
@@ -143,6 +143,9 @@
 if config.has_plugins:
     config.available_features.add("plugins")
 
+if config.link_llvm_dylib:
+    config.available_features.add("llvm-dylib")
+
 if config.build_examples:
     config.available_features.add("examples")
 
diff --git a/lld/test/lit.site.cfg.py.in b/lld/test/lit.site.cfg.py.in
index 703d3b1fd5337..127018166a6ee 100644
--- a/lld/test/lit.site.cfg.py.in
+++ b/lld/test/lit.site.cfg.py.in
@@ -26,6 +26,7 @@ config.ld_lld_default_mingw = @LLD_DEFAULT_LD_LLD_IS_MINGW@
 config.build_examples = @LLVM_BUILD_EXAMPLES@
 config.has_plugins = @LLVM_ENABLE_PLUGINS@
 config.linked_bye_extension = @LLVM_BYE_LINK_INTO_TOOLS@
+config.link_llvm_dylib = @LLVM_LINK_LLVM_DYLIB@
 config.enable_threads = @LLVM_ENABLE_THREADS@
 
 import lit.llvm
diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt
index 908580f791f36..29da2235add96 100644
--- a/llvm/CMakeLists.txt
+++ b/llvm/CMakeLists.txt
@@ -503,6 +503,10 @@ set(LLVM_EXAMPLES_INSTALL_DIR "examples" CACHE STRING
     "Path for examples subdirectory (enabled by LLVM_BUILD_EXAMPLES=ON) 
(defaults to 'examples')")
 mark_as_advanced(LLVM_EXAMPLES_INSTALL_DIR)
 
+set(LLVM_PLUGINS_INSTALL_DIR "plugins" CACHE STRING
+    "Path for plugins subdirectory (defaults to 'plugins')")
+mark_as_advanced(LLVM_PLUGINS_INSTALL_DIR)
+
 # They are used as destination of target generators.
 set(LLVM_RUNTIME_OUTPUT_INTDIR 
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/bin)
 set(LLVM_LIBRARY_OUTPUT_INTDIR 
${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/lib${LLVM_LIBDIR_SUFFIX})
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index 2480d7373d1a3..06916b4a520aa 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -932,7 +932,7 @@ endfunction()
 
 macro(add_llvm_library name)
   cmake_parse_arguments(ARG
-    "SHARED;BUILDTREE_ONLY;MODULE;INSTALL_WITH_TOOLCHAIN;NO_EXPORT"
+    
"SHARED;BUILDTREE_ONLY;MODULE;INSTALL_WITH_TOOLCHAIN;INSTALL_AS_PLUGIN;NO_EXPORT"
     ""
     ""
     ${ARGN})
@@ -972,11 +972,17 @@ macro(add_llvm_library name)
       else()
         get_target_export_arg(${name} LLVM export_to_llvmexports ${umbrella})
       endif()
-      install(TARGETS ${name}
-              ${export_to_llvmexports}
-              LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
-              ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
-              RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT ${name})
+      if(ARG_INSTALL_AS_PLUGIN)
+        install(TARGETS ${name}
+                ${export_to_llvmexports}
+                LIBRARY DESTINATION ${LLVM_PLUGINS_INSTALL_DIR} COMPONENT 
${name})
+      else()
+        install(TARGETS ${name}
+                ${export_to_llvmexports}
+                LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
+                ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX} COMPONENT ${name}
+                RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT 
${name})
+      endif()
 
       if (NOT LLVM_ENABLE_IDE)
         add_llvm_install_targets(install-${name}
@@ -1220,9 +1226,9 @@ function(add_llvm_pass_plugin name)
     endif()
     set_property(GLOBAL APPEND PROPERTY LLVM_STATIC_EXTENSIONS ${name})
   elseif(NOT ARG_NO_MODULE)
-    add_llvm_library(${name} MODULE NO_EXPORT ${ARG_UNPARSED_ARGUMENTS})
+    add_llvm_library(${name} MODULE NO_EXPORT INSTALL_AS_PLUGIN 
${ARG_UNPARSED_ARGUMENTS})
   else()
-    add_llvm_library(${name} OBJECT NO_EXPORT ${ARG_UNPARSED_ARGUMENTS})
+    add_llvm_library(${name} OBJECT NO_EXPORT INSTALL_AS_PLUGIN 
${ARG_UNPARSED_ARGUMENTS})
   endif()
   message(STATUS "Registering ${name} as a pass plugin (static build: 
${LLVM_${name_upper}_LINK_INTO_TOOLS})")
 
diff --git a/llvm/docs/CMake.rst b/llvm/docs/CMake.rst
index 7e95545425f2d..3a69ae42b75b2 100644
--- a/llvm/docs/CMake.rst
+++ b/llvm/docs/CMake.rst
@@ -951,6 +951,11 @@ things to go wrong.  They are also unstable across LLVM 
versions.
   Only matters if *LLVM_INSTALL_UTILS* is enabled.
   Defaults to *LLVM_TOOLS_INSTALL_DIR*.
 
+**LLVM_PLUGINS_INSTALL_DIR**:STRING
+  The path to install LLVM pass-plugins, relative to the 
*CMAKE_INSTALL_PREFIX*.
+  Defaults to *plugins*. Third-party vendors may install out-of-tree plugins
+  here for easy access and implicit version match.
+
 CMake Caches
 ============
 
diff --git a/llvm/test/tools/plugins-shlib/Inputs/foobar.ll 
b/llvm/test/tools/plugins-shlib/Inputs/foobar.ll
new file mode 100644
index 0000000000000..280014f623448
--- /dev/null
+++ b/llvm/test/tools/plugins-shlib/Inputs/foobar.ll
@@ -0,0 +1,2 @@
+define void @foo() { ret void }
+define void @bar() { ret void }
diff --git a/llvm/test/tools/plugins-shlib/Inputs/mymod.py 
b/llvm/test/tools/plugins-shlib/Inputs/mymod.py
new file mode 100644
index 0000000000000..31936a208cf04
--- /dev/null
+++ b/llvm/test/tools/plugins-shlib/Inputs/mymod.py
@@ -0,0 +1,4 @@
+import sys
+
+def pyversion():
+    print(f"Python version: {sys.version}")
diff --git a/llvm/test/tools/plugins-shlib/lit.local.cfg 
b/llvm/test/tools/plugins-shlib/lit.local.cfg
new file mode 100644
index 0000000000000..ac4c2de874905
--- /dev/null
+++ b/llvm/test/tools/plugins-shlib/lit.local.cfg
@@ -0,0 +1,19 @@
+import os
+import sysconfig
+
+# Run end-to-end tests if the reference pass-plugin exists
+pypass_plugin = f"pypass-plugin{config.llvm_shlib_ext}"
+pypass_plugin_shlib = os.path.join(config.llvm_shlib_dir, pypass_plugin)
+if os.path.exists(pypass_plugin_shlib):
+    config.available_features.add("pypass-plugin")
+
+# Disable ASAN's leak detection for Python tests
+config.environment["ASAN_OPTIONS"] = "detect_leaks=0"
+
+libdir = sysconfig.get_config_var("LIBDIR")
+dylib = sysconfig.get_config_var("LDLIBRARY")
+config.substitutions.append(("%libpython", os.path.join(libdir, dylib)))
+
+# FIXME: Use %plugindir globally as %llvmshlibdir is broken in standalone 
builds
+config.substitutions.append(("%plugindir", config.llvm_shlib_dir))
+config.suffixes.add(".py")
diff --git a/llvm/test/tools/plugins-shlib/pypass-functions.py 
b/llvm/test/tools/plugins-shlib/pypass-functions.py
new file mode 100644
index 0000000000000..2bac9877e550c
--- /dev/null
+++ b/llvm/test/tools/plugins-shlib/pypass-functions.py
@@ -0,0 +1,20 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+#
+# We can run on inidivdual functions instead of whole modules
+# RUN: opt -load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:     -pypass-script=%s -pypass-dylib=%libpython \
+# RUN:     -passes=pypass -disable-output %S/Inputs/foobar.ll | FileCheck %s
+
+# CHECK: 0x{{[0-9a-f]+}} Function
+# CHECK: 0x{{[0-9a-f]+}} Function
+
+def registerModulePipelineParsingCallback():
+    """ Call run for each module that goes through the pipeline """
+    return False
+
+def registerFunctionPipelineParsingCallback():
+    """ Call run for each function that goes through the pipeline """
+    return True
+
+def run(input, ctx, stage):
+    print(f"0x{input:016x} {stage}")
diff --git a/llvm/test/tools/plugins-shlib/pypass.py 
b/llvm/test/tools/plugins-shlib/pypass.py
new file mode 100644
index 0000000000000..30f539c03e014
--- /dev/null
+++ b/llvm/test/tools/plugins-shlib/pypass.py
@@ -0,0 +1,34 @@
+# REQUIRES: native, system-linux, llvm-dylib, plugins, pypass-plugin
+
+# We can use environment variables for parameters
+# RUN: env LLVM_PYPASS_DYLIB=%libpython \
+# RUN: env LLVM_PYPASS_SCRIPT=%s \
+# RUN:   opt -load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:       -passes=pypass -disable-output %S/Inputs/foobar.ll | FileCheck %s
+
+# We can use command-line arguments for parameters
+# RUN:   opt -load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:       -pypass-script=%s -pypass-dylib=%libpython \
+# RUN:       -passes=pypass -disable-output %S/Inputs/foobar.ll | FileCheck %s
+
+# Loading the plugin twice causes no issues
+# RUN:   opt -load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:       -load-pass-plugin=%plugindir/pypass-plugin%pluginext \
+# RUN:       -pypass-script=%s -pypass-dylib=%libpython \
+# RUN:       -passes=pypass -disable-output %S/Inputs/foobar.ll | FileCheck %s
+
+# CHECK: Python version: 3
+# CHECK: 0x{{[0-9a-f]+}} Module
+
+# We can import modules relative to the script directory
+from Inputs.mymod import pyversion
+pyversion()
+
+# We don't forward the actual pipeline parsing callback (yet)
+def registerModulePipelineParsingCallback():
+    """ Call run for each module that goes through the pipeline """
+    return True
+
+# We get the addresses of the C API LLVMModule and LLVMContext
+def run(mod, ctx, stage):
+    print(f"0x{mod:016x} {stage}")
diff --git a/llvm/tools/plugins-shlib/CMakeLists.txt 
b/llvm/tools/plugins-shlib/CMakeLists.txt
new file mode 100644
index 0000000000000..62af5b97bc18a
--- /dev/null
+++ b/llvm/tools/plugins-shlib/CMakeLists.txt
@@ -0,0 +1,16 @@
+if (LLVM_ENABLE_PLUGINS)
+  add_llvm_pass_plugin(pypass-plugin INSTALL_WITH_TOOLCHAIN
+    pypass.cpp
+    DEPENDS
+    intrinsics_gen
+  )
+
+  install(TARGETS pypass-plugin
+    LIBRARY DESTINATION "${LLVM_PLUGINS_INSTALL_DIR}"
+  )
+
+  # Don't add a 'lib' prefix. The plugin is no linker input.
+  set_target_properties(pypass-plugin PROPERTIES
+    PREFIX ""
+  )
+endif()
diff --git a/llvm/tools/plugins-shlib/pypass.cpp 
b/llvm/tools/plugins-shlib/pypass.cpp
new file mode 100644
index 0000000000000..0b0179763043c
--- /dev/null
+++ b/llvm/tools/plugins-shlib/pypass.cpp
@@ -0,0 +1,463 @@
+//===- tools/plugins-shlib/pypass.cpp 
-------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Passes/PassBuilder.h"
+#include "llvm/Passes/PassPlugin.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <filesystem>
+#include <memory>
+#include <optional>
+#include <string>
+
+using namespace llvm;
+
+static cl::opt<std::string>
+    DylibPath("pypass-dylib", cl::desc("Path to the Python shared library"),
+              cl::init(""));
+
+static cl::opt<std::string>
+    ScriptPath("pypass-script", cl::desc("Path to the Python script to run"),
+               cl::init(""));
+
+static std::string findPython() {
+  if (!DylibPath.empty())
+    return DylibPath;
+  if (const char *Path = std::getenv("LLVM_PYPASS_DYLIB"))
+    return std::string(Path);
+  // TODO: Run Python from PATH and use a script to query the shared lib
+  return std::string{};
+}
+
+static std::string findScript() {
+  if (!ScriptPath.empty())
+    return ScriptPath;
+  if (const char *Path = std::getenv("LLVM_PYPASS_SCRIPT"))
+    return std::string(Path);
+  return std::string{};
+}
+
+struct PythonAPI {
+  using Py_InitializeEx_t = void(int);
+  using Py_Initialize_t = void(void);
+  using Py_FinalizeEx_t = int(void);
+  using Py_Finalize_t = void(void);
+  using PyDict_GetItemString_t = void *(void *, const char *);
+  using PyGILStateEnsure_t = int();
+  using PyGILStateRelease_t = void(int);
+  using PyImport_AddModule_t = void *(const char *);
+  using PyLong_FromVoidPtr_t = void *(void *);
+  using PyUnicode_FromString_t = void *(const char *);
+  using PyModule_GetDict_t = void *(void *);
+  using PyObject_CallObject_t = void *(void *, void *);
+  using PyObject_IsTrue_t = int(void *);
+  using PyRun_SimpleString_t = int(const char *);
+  using PyTuple_SetItem_t = int(void *, long, void *);
+  using PyTuple_New_t = void *(long);
+  using PyTypeObject_t = void *;
+
+  // pylifecycle.h
+  Py_InitializeEx_t *Py_InitializeEx;
+  Py_Initialize_t *Py_Initialize;
+  Py_FinalizeEx_t *Py_FinalizeEx;
+  Py_Finalize_t *Py_Finalize;
+
+  // pythonrun.h
+  PyRun_SimpleString_t *PyRun_SimpleString;
+
+  // pystate.h
+  PyGILStateEnsure_t *PyGILState_Ensure;
+  PyGILStateRelease_t *PyGILState_Release;
+
+  // import.h
+  PyImport_AddModule_t *PyImport_AddModule;
+
+  // object.h
+  PyObject_IsTrue_t *PyObject_IsTrue;
+
+  // moduleobject.h
+  PyModule_GetDict_t *PyModule_GetDict;
+
+  // dictobject.h
+  PyDict_GetItemString_t *PyDict_GetItemString;
+
+  // abstract.h
+  PyObject_CallObject_t *PyObject_CallObject;
+
+  // longobject.h
+  PyLong_FromVoidPtr_t *PyLong_FromVoidPtr;
+
+  // unicodeobject.h
+  PyUnicode_FromString_t *PyUnicode_FromString;
+
+  // tupleobject.h
+  PyTuple_SetItem_t *PyTuple_SetItem;
+  PyTuple_New_t *PyTuple_New;
+
+  PythonAPI() : Ready(false) {
+    if (!loadDylib(findPython()))
+      return;
+    if (!resolveSymbols())
+      return;
+    if (Py_InitializeEx) {
+      Py_InitializeEx(0);
+    } else {
+      Py_Initialize();
+    }
+    Ready = true;
+  }
+
+  ~PythonAPI() {
+    if (std::atomic_exchange(&Ready, false)) {
+      if (Py_FinalizeEx) {
+        Py_FinalizeEx();
+      } else {
+        Py_Finalize();
+      }
+    }
+  }
+
+  bool loadDylib(std::string Path) {
+    std::string Err;
+    Dylib = sys::DynamicLibrary::getPermanentLibrary(Path.c_str(), &Err);
+    if (!Dylib.isValid()) {
+      errs() << "dlopen for '" << Path << "' failed: " << Err << "\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  bool resolveSymbols() {
+    bool Success = true;
+    Success &= resolve("Py_InitializeEx", &Py_InitializeEx);
+    Success &= resolve("Py_Initialize", &Py_Initialize);
+    Success &= resolve("Py_FinalizeEx", &Py_FinalizeEx);
+    Success &= resolve("Py_Finalize", &Py_Finalize);
+    Success &= resolve("PyGILState_Ensure", &PyGILState_Ensure);
+    Success &= resolve("PyGILState_Release", &PyGILState_Release);
+    Success &= resolve("PyRun_SimpleString", &PyRun_SimpleString);
+    Success &= resolve("PyImport_AddModule", &PyImport_AddModule);
+    Success &= resolve("PyModule_GetDict", &PyModule_GetDict);
+    Success &= resolve("PyDict_GetItemString", &PyDict_GetItemString);
+    Success &= resolve("PyObject_CallObject", &PyObject_CallObject);
+    Success &= resolve("PyObject_IsTrue", &PyObject_IsTrue);
+    Success &= resolve("PyLong_FromVoidPtr", &PyLong_FromVoidPtr);
+    Success &= resolve("PyUnicode_FromString", &PyUnicode_FromString);
+    Success &= resolve("PyTuple_SetItem", &PyTuple_SetItem);
+    Success &= resolve("PyTuple_New", &PyTuple_New);
+    return Success;
+  }
+
+  bool isReady() const { return Ready; }
+
+  bool loadScript(const std::string &ScriptPath) const {
+    std::string LoadCmd;
+    raw_string_ostream(LoadCmd)
+        << "import runpy\n"
+        << "globals().update(runpy.run_path('" << ScriptPath << "'))";
+
+    if (PyRun_SimpleString(LoadCmd.c_str()) != 0) {
+      errs() << "Failed to load script: " << ScriptPath << "\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  bool addImportSearchPath(std::string Path) const {
+    std::string LoadCmd;
+    raw_string_ostream(LoadCmd) << "import sys\n"
+                                << "sys.path.append('" << Path << "')";
+    // Interpreter is not thread-safe
+    auto GIL = make_scope_exit(
+        [this, Lock = PyGILState_Ensure()]() { PyGILState_Release(Lock); });
+    if (PyRun_SimpleString(LoadCmd.c_str()) != 0) {
+      errs() << "Failed to add import search path: " << Path << "\n";
+      return false;
+    }
+
+    return true;
+  }
+
+  void *addModule(const char *Name) const {
+    void *Mod = PyImport_AddModule(Name);
+    return PyModule_GetDict(Mod);
+  }
+
+  // Very simple interface to execute a Python function
+  bool invoke(void *Mod, const char *Name, void *Args = nullptr) const {
+    // If the function doesn't exist, we assume no
+    void *Fn = PyDict_GetItemString(Mod, Name);
+    if (!Fn)
+      return false;
+    // Interpreter is not thread-safe
+    auto GIL = make_scope_exit(
+        [this, Lock = PyGILState_Ensure()]() { PyGILState_Release(Lock); });
+    // If we get no result, there was an error in Python
+    void *Result = PyObject_CallObject(Fn, Args);
+    if (!Result) {
+      errs() << "PyPassContext error: " << Name << "() failed\n";
+      return false;
+    }
+    // If the result is truthy, then it's a yes
+    return PyObject_IsTrue(Result);
+  }
+
+private:
+  sys::DynamicLibrary Dylib;
+  std::atomic<bool> Ready;
+
+  template <typename FnTy> bool resolve(const char *Name, FnTy **Var) {
+    assert(Dylib.isValid() && "dlopen shared library first");
+    assert(*Var == nullptr && "Resolve symbols once");
+    if (void *FnPtr = Dylib.getAddressOfSymbol(Name)) {
+      *Var = reinterpret_cast<FnTy *>(FnPtr);
+      return true;
+    }
+    errs() << "Missing required CPython API symbol '" << Name
+           << "' in: " << DylibPath << "\n";
+    return false;
+  };
+};
+
+// Python interface is initialized on first access and it is shared across all
+// threads. It can be used like a state-less thread-safe object.
+const PythonAPI &getPyAPI() {
+  static const PythonAPI PyAPI;
+  return PyAPI;
+}
+
+class PyPassContext {
+public:
+  PyPassContext() : PyAPI(getPyAPI()) {}
+
+  bool loadScript(const std::string &Path) {
+    if (!PyAPI.isReady())
+      return false;
+
+    // Make relative paths resolve naturally in import statements
+    std::string Dir = std::filesystem::path(Path).parent_path().u8string();
+    if (!PyAPI.addImportSearchPath(Dir))
+      return false;
+
+    if (!PyAPI.loadScript(Path))
+      return false;
+
+    PyGlobals = PyAPI.addModule("__main__");
+    PyBuiltins = PyAPI.addModule("builtins");
+    if (!PyGlobals || !PyBuiltins)
+      return false;
+
+    return PyAPI.PyDict_GetItemString(PyGlobals, "run");
+  }
+
+  bool registerEP(std::string Name) {
+    std::string EP = "register" + Name;
+    return PyAPI.invoke(PyGlobals, EP.c_str());
+  }
+
+  bool run(void *Entity, void *Ctx, const char *Stage) {
+    void *Args = PyAPI.PyTuple_New(3);
+    if (!Args)
+      return false;
+    if (PyAPI.PyTuple_SetItem(Args, 0, PyAPI.PyLong_FromVoidPtr(Entity)) != 0)
+      return false;
+    if (PyAPI.PyTuple_SetItem(Args, 1, PyAPI.PyLong_FromVoidPtr(Ctx)) != 0)
+      return false;
+    if (PyAPI.PyTuple_SetItem(Args, 2, PyAPI.PyUnicode_FromString(Stage)) != 0)
+      return false;
+
+    // TODO: Should we expose PyPassContext and/or arguments from specific
+    // entry-points like OptLevel, ThinOrFullLTOPhase or InnerPipeline?
+
+    return PyAPI.invoke(PyGlobals, "run", Args);
+  }
+
+private:
+  const PythonAPI &PyAPI;
+  void *PyGlobals;
+  void *PyBuiltins;
+};
+
+struct PyPass : PassInfoMixin<PyPass> {
+  PyPass(std::shared_ptr<PyPassContext> Context, StringRef EP)
+      : Stage(EP.str()), Context(std::move(Context)) {}
+
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) {
+    LLVMModuleRef Mod = wrap(&M);
+    LLVMContextRef Ctx = wrap(&M.getContext());
+    bool Changed = Context->run(Mod, Ctx, Stage.c_str());
+    return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
+  }
+
+  PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
+    LLVMValueRef Fn = wrap(&F);
+    LLVMContextRef Ctx = wrap(&F.getContext());
+    bool Changed = Context->run(Fn, Ctx, Stage.c_str());
+    return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
+  }
+
+  std::string Stage;
+  std::shared_ptr<PyPassContext> Context;
+  std::optional<OptimizationLevel> OptLevel;
+  std::optional<ThinOrFullLTOPhase> LTOPhase;
+  std::optional<ArrayRef<llvm::PassBuilder::PipelineElement>> InnerPipeline;
+};
+
+static void registerCallbacks(PassBuilder &PB) {
+  // Context is shared across all entry-points in this pipeline
+  auto Context = std::make_shared<PyPassContext>();
+
+  // All entry-point callbacks run the same script
+  std::string ScriptPath = findScript();
+  if (!Context->loadScript(ScriptPath))
+    return;
+
+  // Create one PyPass instance per entry-point
+  if (Context->registerEP("PipelineStartEPCallback"))
+    PB.registerPipelineStartEPCallback(
+        [Context](ModulePassManager &MPM, OptimizationLevel Opt) {
+          PyPass P(Context, "PipelineStartEPCallback");
+          P.OptLevel = Opt;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("PipelineEarlySimplificationEPCallback"))
+    PB.registerPipelineEarlySimplificationEPCallback(
+        [Context](ModulePassManager &MPM, OptimizationLevel Opt,
+                  ThinOrFullLTOPhase Phase) {
+          PyPass P(Context, "PipelineEarlySimplificationEPCallback");
+          P.OptLevel = Opt;
+          P.LTOPhase = Phase;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("OptimizerEarlyEPCallback"))
+    PB.registerOptimizerEarlyEPCallback([Context](ModulePassManager &MPM,
+                                                  OptimizationLevel Opt,
+                                                  ThinOrFullLTOPhase Phase) {
+      PyPass P(Context, "OptimizerEarlyEPCallback");
+      P.OptLevel = Opt;
+      P.LTOPhase = Phase;
+      MPM.addPass(std::move(P));
+      return true;
+    });
+
+  if (Context->registerEP("OptimizerLastEPCallback"))
+    PB.registerOptimizerLastEPCallback([Context](ModulePassManager &MPM,
+                                                 OptimizationLevel Opt,
+                                                 ThinOrFullLTOPhase Phase) {
+      PyPass P(Context, "OptimizerLastEPCallback");
+      P.OptLevel = Opt;
+      P.LTOPhase = Phase;
+      MPM.addPass(std::move(P));
+      return true;
+    });
+
+  if (Context->registerEP("PeepholeEPCallback"))
+    PB.registerPeepholeEPCallback(
+        [Context](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          PyPass P(Context, "PeepholeEPCallback");
+          P.OptLevel = Opt;
+          FPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("ScalarOptimizerLateEPCallback"))
+    PB.registerScalarOptimizerLateEPCallback(
+        [Context](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          PyPass P(Context, "ScalarOptimizerLateEPCallback");
+          P.OptLevel = Opt;
+          FPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("VectorizerStartEPCallback"))
+    PB.registerVectorizerStartEPCallback(
+        [Context](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          PyPass P(Context, "VectorizerStartEPCallback");
+          P.OptLevel = Opt;
+          FPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("VectorizerEndEPCallback"))
+    PB.registerVectorizerEndEPCallback(
+        [Context](FunctionPassManager &FPM, OptimizationLevel Opt) {
+          PyPass P(Context, "VectorizerEndEPCallback");
+          P.OptLevel = Opt;
+          FPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("FullLinkTimeOptimizationEarlyEPCallback"))
+    PB.registerFullLinkTimeOptimizationEarlyEPCallback(
+        [Context](ModulePassManager &MPM, OptimizationLevel Opt) {
+          PyPass P(Context, "FullLinkTimeOptimizationEarlyEPCallback");
+          P.OptLevel = Opt;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("FullLinkTimeOptimizationLastEPCallback"))
+    PB.registerFullLinkTimeOptimizationLastEPCallback(
+        [Context](ModulePassManager &MPM, OptimizationLevel Opt) {
+          PyPass P(Context, "FullLinkTimeOptimizationLastEPCallback");
+          P.OptLevel = Opt;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("ModulePipelineParsingCallback"))
+    PB.registerPipelineParsingCallback(
+        [Context](StringRef Name, ModulePassManager &MPM,
+                  ArrayRef<llvm::PassBuilder::PipelineElement> InnerPipeline) {
+          if (Name.lower() != "pypass")
+            return false;
+          PyPass P(Context, "Module");
+          P.InnerPipeline = InnerPipeline;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+
+  if (Context->registerEP("FunctionPipelineParsingCallback"))
+    PB.registerPipelineParsingCallback(
+        [Context](StringRef Name, FunctionPassManager &FPM,
+                  ArrayRef<llvm::PassBuilder::PipelineElement> InnerPipeline) {
+          if (Name.lower() != "pypass")
+            return false;
+          PyPass P(Context, "Function");
+          P.InnerPipeline = InnerPipeline;
+          FPM.addPass(std::move(P));
+          return true;
+        });
+
+  // TODO: This is invoked as fallback if neither module nor function parsing
+  // yields a result. Does it make sense for plugins?
+  if (Context->registerEP("TopLevelPipelineParsingCallback"))
+    PB.registerParseTopLevelPipelineCallback(
+        [Context](ModulePassManager &MPM,
+                  ArrayRef<llvm::PassBuilder::PipelineElement> InnerPipeline) {
+          PyPass P(Context, "Module");
+          P.InnerPipeline = InnerPipeline;
+          MPM.addPass(std::move(P));
+          return true;
+        });
+}
+
+extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK
+llvmGetPassPluginInfo() {
+  return {LLVM_PLUGIN_API_VERSION, "PyPass", LLVM_VERSION_STRING,
+          registerCallbacks};
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to