https://github.com/vchuravy updated https://github.com/llvm/llvm-project/pull/212195
>From 832eab1d2de0343c0085d203a283774b6ce96bd3 Mon Sep 17 00:00:00 2001 From: Valentin Churavy <[email protected]> Date: Wed, 22 Jul 2026 17:44:14 +0200 Subject: [PATCH] [flang] Add a pass-pipeline config callback hook for plugins The HLFIR-to-FIR pipeline extension points live on MLIRToLLVMPassPipelineConfig, which the frontend builds as a local of CodeGenAction, out of reach of a plugin. Add a process-global registry of callbacks that run on the config before the pipeline is built. A plugin registers one from a static initializer, so it is in place before any compilation begins, as FrontendPluginRegistry does for plugin actions. Both code generation entry points invoke the callbacks, lowerHLFIRToFIR for -emit-fir and generateLLVMIR for -emit-llvm/-emit-obj, and are mutually exclusive for a given compilation, so a plugin sees the same behaviour whichever output was asked for. Co-Authored-By: Claude Opus 5 <[email protected]> --- flang/docs/FlangDriver.md | 30 +++++++ .../flang/Optimizer/Passes/Pipelines.h | 11 +++ flang/lib/Frontend/FrontendActions.cpp | 5 ++ flang/lib/Optimizer/Passes/Pipelines.cpp | 17 ++++ .../Optimizer/HLFIRExtensionPointsTest.cpp | 80 ++++++++++++++++++- 5 files changed, 142 insertions(+), 1 deletion(-) diff --git a/flang/docs/FlangDriver.md b/flang/docs/FlangDriver.md index a2eb73002d798..9a03b1f9b6ba0 100644 --- a/flang/docs/FlangDriver.md +++ b/flang/docs/FlangDriver.md @@ -546,6 +546,36 @@ 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` 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`), so registering once is enough. The registry +is append-only and runs callbacks in registration order. + ## 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 c50d41844941e..8d269f162d0ea 100644 --- a/flang/include/flang/Optimizer/Passes/Pipelines.h +++ b/flang/include/flang/Optimizer/Passes/Pipelines.h @@ -132,6 +132,17 @@ void addLLVMDialectToLLVMPass(mlir::PassManager &pm, llvm::raw_ostream &output); /// Use inliner extension point callback to register the default inliner pass. void registerDefaultInlinerPass(MLIRToLLVMPassPipelineConfig &config); +/// Register a callback that augments the MLIRToLLVMPassPipelineConfig before +/// the frontend builds the pipeline. Use this to add passes at the pipeline +/// extension points from a plugin. Call from a static initializer; callbacks +/// run in registration order. +void registerPassPipelineConfigCallback( + std::function<void(MLIRToLLVMPassPipelineConfig &)> 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 8955a8f61e513..adbbfaa69a0cf 100644 --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -633,6 +633,8 @@ void CodeGenAction::lowerHLFIRToFIR() { ci.getInvocation().getLoweringOpts().getFPMaxminBehavior(); if (ci.getInvocation().getLangOpts().OpenMPIsTargetDevice) config.EnableOpenMPIsTargetDevice = 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); @@ -751,6 +753,9 @@ void CodeGenAction::generateLLVMIR() { config.SkipConvertComplexPow = pipelineTriple.isAMDGCN(); fir::registerDefaultInlinerPass(config); + // Give plugins a chance to register passes at the extension points. + fir::invokePassPipelineConfigCallbacks(config); + if (auto vsr = getVScaleRange(ci)) { config.VScaleMin = vsr->first; config.VScaleMax = vsr->second; diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp index 15a342e10fc7f..46c801ede5eb4 100644 --- a/flang/lib/Optimizer/Passes/Pipelines.cpp +++ b/flang/lib/Optimizer/Passes/Pipelines.cpp @@ -171,6 +171,23 @@ void registerDefaultInlinerPass(MLIRToLLVMPassPipelineConfig &config) { }); } +static std::vector<std::function<void(MLIRToLLVMPassPipelineConfig &)>> & +getPassPipelineConfigCallbacks() { + static std::vector<std::function<void(MLIRToLLVMPassPipelineConfig &)>> + callbacks; + return callbacks; +} + +void registerPassPipelineConfigCallback( + std::function<void(MLIRToLLVMPassPipelineConfig &)> callback) { + getPassPipelineConfigCallbacks().push_back(std::move(callback)); +} + +void invokePassPipelineConfigCallbacks(MLIRToLLVMPassPipelineConfig &config) { + for (auto &callback : getPassPipelineConfigCallbacks()) + callback(config); +} + /// Create a pass pipeline for running default optimization passes for /// incremental conversion of FIR. /// diff --git a/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp b/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp index 035efc1606a3b..9af355b686e63 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,81 @@ 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; + bool epRan = false; + + void reset() { + order.clear(); + seenConfig = nullptr; + epRan = false; + } + /// 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; + } +}; + +ConfigCallbackRecorder &recorder() { + static ConfigCallbackRecorder r; + return r; +} + +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) { + recorder().epRan = true; + 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_TRUE(recorder().epRan); + EXPECT_NE(pipeline.find("ep-marker"), std::string::npos) << pipeline; +} + } // namespace _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
