[flang-commits] [flang] 140425b - [flang] Add a MLIRToLLVMPassPipeline callbacks for plugins (#212195)
via flang-commits
flang-commits at lists.llvm.org
Fri Oct 2 19:52:01 PDT 2026
Author: Valentin Churavy
Date: 2026-10-03T02:51:49Z
New Revision: 140425be9bb3d80aa6e9c00e34ca516140ec1ff8
URL: https://github.com/llvm/llvm-project/commit/140425be9bb3d80aa6e9c00e34ca516140ec1ff8
DIFF: https://github.com/llvm/llvm-project/commit/140425be9bb3d80aa6e9c00e34ca516140ec1ff8.diff
LOG: [flang] Add a MLIRToLLVMPassPipeline callbacks for plugins (#212195)
Expose the extension points of the HLFIR-to-FIR pass pipeline exposes extension points to plugins.
Follow-up to #212194
Assisted-by: Claude Code (Opus 5, Opus 5.5)
Added:
Modified:
flang/docs/FlangDriver.md
flang/include/flang/Optimizer/Passes/Pipelines.h
flang/lib/Frontend/FrontendActions.cpp
flang/lib/Optimizer/Passes/Pipelines.cpp
flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
Removed:
################################################################################
diff --git a/flang/docs/FlangDriver.md b/flang/docs/FlangDriver.md
index c9df05ded7f492..cfed20ebe60e97 100644
--- a/flang/docs/FlangDriver.md
+++ b/flang/docs/FlangDriver.md
@@ -560,6 +560,40 @@ config.registerHLFIROptEarlyEPCallbacks(
});
```
+### Registering Extension Point Passes from a Plugin
+
+To add passes at these extension points from a
+[plugin](#frontend-driver-plugins), register a *pipeline config callback* with
+`fir::registerPassPipelineConfigCallback`
+(`flang/include/flang/Optimizer/Passes/Pipelines.h`). The frontend driver runs
+every registered callback on its `MLIRToLLVMPassPipelineConfig` once the config
+is fully set up, just before it builds the pipeline. Register from a static initializer, so the callback is in place as
+soon as the plugin is loaded and before any compilation begins:
+
+```c++
+struct MyPluginRegistration {
+ MyPluginRegistration() {
+ fir::registerPassPipelineConfigCallback(
+ [](MLIRToLLVMPassPipelineConfig &config) {
+ config.registerHLFIROptEarlyEPCallbacks(
+ [](mlir::PassManager &pm, llvm::OptimizationLevel) {
+ pm.addPass(createMyHLFIRPass());
+ });
+ });
+ }
+};
+static MyPluginRegistration myPluginRegistration;
+```
+
+These callbacks run on both the `-emit-fir` path
+(`CodeGenAction::lowerHLFIRToFIR`) and the `-emit-llvm`/`-emit-obj` path
+(`CodeGenAction::generateLLVMIR`). `-emit-fir` only builds the HLFIR-to-FIR
+pipeline, so passes registered at the HLFIR extension points run on both paths,
+while those registered at the FIR optimizer extension points run only when
+generating LLVM IR. The registry is append-only and runs callbacks in
+registration order; a callback must not register further callbacks. Only the
+frontend driver runs the registry: `bbc`, `tco` and `fir-opt` do not.
+
## LLVM Pass Plugins
Pass plugins are dynamic shared objects that consist of one or more LLVM IR
diff --git a/flang/include/flang/Optimizer/Passes/Pipelines.h b/flang/include/flang/Optimizer/Passes/Pipelines.h
index ce4af1b6a1f369..f071f52c1477ef 100644
--- a/flang/include/flang/Optimizer/Passes/Pipelines.h
+++ b/flang/include/flang/Optimizer/Passes/Pipelines.h
@@ -28,6 +28,7 @@
#include "llvm/Frontend/Debug/Options.h"
#include "llvm/Passes/OptimizationLevel.h"
#include "llvm/Support/CommandLine.h"
+#include <functional>
namespace fir {
@@ -130,6 +131,21 @@ void addLLVMDialectToLLVMPass(mlir::PassManager &pm, llvm::raw_ostream &output);
/// Use inliner extension point callback to register the default inliner pass.
void registerDefaultInlinerPass(MLIRToLLVMPassPipelineConfig &config);
+/// A callback run on the MLIRToLLVMPassPipelineConfig before the frontend
+/// builds the pipeline.
+using PassPipelineConfigCallback =
+ std::function<void(MLIRToLLVMPassPipelineConfig &)>;
+
+/// Register a callback that augments the MLIRToLLVMPassPipelineConfig before
+/// the frontend builds the pipeline. Call this from a static initializer in a
+/// plugin to add passes at the pipeline extension points. The callbacks are run
+/// in registration order, and must not themselves register callbacks.
+void registerPassPipelineConfigCallback(PassPipelineConfigCallback callback);
+
+/// Run the callbacks registered via registerPassPipelineConfigCallback on
+/// \p config.
+void invokePassPipelineConfigCallbacks(MLIRToLLVMPassPipelineConfig &config);
+
/// Register the passes used in Flang's MLIR pass pipeline
/// e.g. --mlir-print-ir-before=<pass> and similar.
void registerFlangPipelinePasses();
diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp
index 1e29f4f430a1e3..06d67e9417bc44 100644
--- a/flang/lib/Frontend/FrontendActions.cpp
+++ b/flang/lib/Frontend/FrontendActions.cpp
@@ -653,6 +653,8 @@ void CodeGenAction::lowerHLFIRToFIR() {
if (ci.getInvocation().getFortranOpts().features.IsEnabled(
Fortran::common::LanguageFeature::CUDA))
config.EnableCUDA = true;
+ // Give plugins a chance to register passes at the extension points.
+ fir::invokePassPipelineConfigCallbacks(config);
// Create the pass pipeline
fir::createHLFIRToFIRPassPipeline(pm, enableOpenMP, config);
(void)mlir::applyPassManagerCLOptions(pm);
@@ -798,6 +800,10 @@ void CodeGenAction::generateLLVMIR() {
config.ComplexRange = opts.getComplexRange();
+ // Give plugins a chance to register passes at the extension points, once the
+ // config is fully set up.
+ fir::invokePassPipelineConfigCallbacks(config);
+
// Create the pass pipeline
fir::createMLIRToLLVMPassPipeline(pm, config, getCurrentFile());
(void)mlir::applyPassManagerCLOptions(pm);
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 65fed60feba620..d965400e028d24 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -17,6 +17,7 @@
#include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
#include "mlir/Dialect/OpenMP/Transforms/Passes.h"
#include "llvm/Support/CommandLine.h"
+#include <vector>
/// Force setting the no-alias attribute on fuction arguments when possible.
static llvm::cl::opt<bool> forceNoAlias("force-no-alias", llvm::cl::Hidden,
@@ -165,6 +166,21 @@ void registerDefaultInlinerPass(MLIRToLLVMPassPipelineConfig &config) {
});
}
+static std::vector<PassPipelineConfigCallback> &
+getPassPipelineConfigCallbacks() {
+ static std::vector<PassPipelineConfigCallback> callbacks;
+ return callbacks;
+}
+
+void registerPassPipelineConfigCallback(PassPipelineConfigCallback callback) {
+ getPassPipelineConfigCallbacks().push_back(std::move(callback));
+}
+
+void invokePassPipelineConfigCallbacks(MLIRToLLVMPassPipelineConfig &config) {
+ for (PassPipelineConfigCallback &callback : getPassPipelineConfigCallbacks())
+ callback(config);
+}
+
void createDefaultFIRPreCFGOptimizerPassPipeline(
mlir::PassManager &pm, MLIRToLLVMPassPipelineConfig &pc) {
// simplify the IR
diff --git a/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp b/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
index 035efc1606a3be..4ca3a6ecbfb2f0 100644
--- a/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
+++ b/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
@@ -6,7 +6,8 @@
//
//===----------------------------------------------------------------------===//
//
-// Tests for the HLFIR extension points of the HLFIR-to-FIR pass pipeline.
+// Tests for the HLFIR extension points of the HLFIR-to-FIR pass pipeline, and
+// for the pipeline config callback registry plugins use to reach them.
//
// The callbacks run when the pipeline is built, so no IR is needed: building
// the pipeline is enough to observe them.
@@ -127,4 +128,76 @@ TEST(HLFIRExtensionPoint, MarkersAreAtTheDocumentedPositions) {
EXPECT_LT(lastMarker, lowerIntrinsics) << pipeline;
}
+// The registry is process-global and append-only, so a callback capturing a
+// local by reference would be re-invoked by a later test with the referent
+// destroyed. Tests record into this process-lifetime recorder instead, and each
+// asserts only on the markers it wrote, so they do not depend on test order.
+struct ConfigCallbackRecorder {
+ std::vector<std::string> order;
+ MLIRToLLVMPassPipelineConfig *seenConfig = nullptr;
+
+ void reset() {
+ order.clear();
+ seenConfig = nullptr;
+ }
+ /// Index of \p marker in `order`, or npos.
+ size_t indexOf(llvm::StringRef marker) const {
+ for (size_t i = 0, e = order.size(); i != e; ++i)
+ if (order[i] == marker)
+ return i;
+ return std::string::npos;
+ }
+};
+
+// Callbacks are registered and run from test bodies, never during static
+// initialization, so a plain global is safe.
+ConfigCallbackRecorder recorder;
+
+TEST(PassPipelineConfigCallback, CallbacksRunInRegistrationOrderOnTheConfig) {
+ fir::registerPassPipelineConfigCallback(
+ [](MLIRToLLVMPassPipelineConfig &config) {
+ recorder.order.push_back("order-first");
+ recorder.seenConfig = &config;
+ });
+ fir::registerPassPipelineConfigCallback([](MLIRToLLVMPassPipelineConfig &) {
+ recorder.order.push_back("order-second");
+ });
+
+ recorder.reset();
+ MLIRToLLVMPassPipelineConfig config(llvm::OptimizationLevel::O2);
+ fir::invokePassPipelineConfigCallbacks(config);
+
+ size_t first = recorder.indexOf("order-first");
+ size_t second = recorder.indexOf("order-second");
+ ASSERT_NE(first, std::string::npos);
+ ASSERT_NE(second, std::string::npos);
+ EXPECT_LT(first, second);
+ EXPECT_EQ(recorder.seenConfig, &config);
+}
+
+// The plugin shape: the config callback registers an extension point
+// callback, which then contributes a pass when the pipeline is built.
+TEST(PassPipelineConfigCallback, CanRegisterHLFIRExtensionPoints) {
+ fir::registerPassPipelineConfigCallback(
+ [](MLIRToLLVMPassPipelineConfig &config) {
+ config.registerHLFIROptEarlyEPCallbacks(
+ [](mlir::PassManager &pm, llvm::OptimizationLevel) {
+ pm.addPass(std::make_unique<MarkerPass>());
+ });
+ });
+
+ recorder.reset();
+ mlir::MLIRContext context;
+ mlir::PassManager pm(&context, mlir::ModuleOp::getOperationName());
+ MLIRToLLVMPassPipelineConfig config(llvm::OptimizationLevel::O2);
+ fir::invokePassPipelineConfigCallbacks(config);
+ fir::createHLFIRToFIRPassPipeline(pm, fir::EnableOpenMP::None, config);
+
+ std::string pipeline;
+ llvm::raw_string_ostream os(pipeline);
+ pm.printAsTextualPipeline(os);
+
+ EXPECT_NE(pipeline.find("ep-marker"), std::string::npos) << pipeline;
+}
+
} // namespace
More information about the flang-commits
mailing list