https://github.com/vchuravy updated https://github.com/llvm/llvm-project/pull/212196
>From 3e8c6925cc0b5f40b0d6231a80bef8e5b317a950 Mon Sep 17 00:00:00 2001 From: Valentin Churavy <[email protected]> Date: Sun, 26 Jul 2026 23:53:30 +0200 Subject: [PATCH] [flang] Add an example plugin exercising the HLFIR pipeline extension points The HLFIR extension points, the pipeline config callback registry and fir-opt's symbol export all exist to let an out-of-tree MLIR pass run while the HLFIR intrinsic operations (hlfir.sum, hlfir.matmul, ...) are still present. None of that was covered end to end, and nothing showed how to use it. Add flang/examples/HLFIRPipelinePlugin, modelled on PrintFlangFunctionNames. It contributes a pass that prints the HLFIR operations still present in the module, tagged with the pipeline position it was inserted at, and exposes it through both plugin entry points: a static initializer calling fir::registerPassPipelineConfigCallback for the `flang -fc1 -load` path, and mlirGetPassPluginInfo so fir-opt can load it with --load-pass-plugin. Nothing is linked into the shared object; MLIR, FIR and flang symbols resolve against the host tool, which is what export_executable_symbols_for_plugins on flang and fir-opt provides. Two tests use it. Examples/hlfir-pipeline-plugin.f90 covers -emit-fir and -emit-llvm, so both callback invocation sites, plus a no-plugin negative check. It checks -O0 and -O2 because at -O0 the intrinsics reach both extension points, while at -O2 SimplifyHLFIRIntrinsics has expanded them before the Last one - which is why both points exist. Examples/fir-opt-pass-plugin.fir covers --load-pass-plugin and the pass showing up in fir-opt's registry. Co-Authored-By: Claude Opus 5 <[email protected]> --- flang/docs/FlangDriver.md | 17 +++ flang/examples/CMakeLists.txt | 1 + .../HLFIRPipelinePlugin/CMakeLists.txt | 15 +++ .../HLFIRPipelinePlugin.cpp | 111 ++++++++++++++++++ flang/test/CMakeLists.txt | 1 + flang/test/Examples/fir-opt-pass-plugin.fir | 34 ++++++ flang/test/Examples/hlfir-pipeline-plugin.f90 | 51 ++++++++ 7 files changed, 230 insertions(+) create mode 100644 flang/examples/HLFIRPipelinePlugin/CMakeLists.txt create mode 100644 flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp create mode 100644 flang/test/Examples/fir-opt-pass-plugin.fir create mode 100644 flang/test/Examples/hlfir-pipeline-plugin.f90 diff --git a/flang/docs/FlangDriver.md b/flang/docs/FlangDriver.md index ffafb02273265..80b4ce397ebdd 100644 --- a/flang/docs/FlangDriver.md +++ b/flang/docs/FlangDriver.md @@ -593,6 +593,23 @@ These callbacks run on both the `-emit-fir` path (`CodeGenAction::generateLLVMIR`), so registering once is enough. The registry is append-only and runs callbacks in registration order. +There is an example plugin in `flang/examples/HLFIRPipelinePlugin`, tested by +`flang/test/Examples/hlfir-pipeline-plugin.f90`. + +### Loading MLIR Pass Plugins into `fir-opt` + +`fir-opt` is built on `MlirOptMain` and therefore accepts MLIR's +`--load-pass-plugin` and `--load-dialect-plugin` options. As with `mlir-opt`, +the plugin resolves the MLIR, FIR and HLFIR symbols it uses against the host +tool, which therefore has to export them +(`export_executable_symbols_for_plugins` in +`flang/tools/fir-opt/CMakeLists.txt`): + +```bash +fir-opt --load-pass-plugin=./MyPasses.so \ + --pass-pipeline='builtin.module(my-hlfir-pass)' input.fir +``` + ## LLVM Pass Plugins Pass plugins are dynamic shared objects that consist of one or more LLVM IR diff --git a/flang/examples/CMakeLists.txt b/flang/examples/CMakeLists.txt index 746a6f23ae015..c91cfd82aff9e 100644 --- a/flang/examples/CMakeLists.txt +++ b/flang/examples/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(HLFIRPipelinePlugin) add_subdirectory(PrintFlangFunctionNames) add_subdirectory(FlangOmpReport) add_subdirectory(FeatureList) diff --git a/flang/examples/HLFIRPipelinePlugin/CMakeLists.txt b/flang/examples/HLFIRPipelinePlugin/CMakeLists.txt new file mode 100644 index 0000000000000..af46cd87ed077 --- /dev/null +++ b/flang/examples/HLFIRPipelinePlugin/CMakeLists.txt @@ -0,0 +1,15 @@ +# TODO: Note that this is currently only available on Linux. +# On Windows, we would also have to specify e.g. `PLUGIN_TOOL`. +# +# Nothing is linked in on purpose: the MLIR, FIR and flang symbols the plugin +# uses are resolved against the host tool (`flang -fc1` or `fir-opt`) when the +# shared object is loaded, which is why both export their symbols for plugins. +add_llvm_example_library(flangHLFIRPipelinePlugin + MODULE + HLFIRPipelinePlugin.cpp + + DEPENDS + acc_gen + flangPasses + omp_gen +) diff --git a/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp b/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp new file mode 100644 index 0000000000000..be93de897cdf8 --- /dev/null +++ b/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp @@ -0,0 +1,111 @@ +//===-- HLFIRPipelinePlugin.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 +// +//===----------------------------------------------------------------------===// +// +// Example plugin adding an out-of-tree MLIR pass to flang's HLFIR-to-FIR pass +// pipeline, at the points where the HLFIR intrinsic operations (hlfir.sum, +// hlfir.matmul, ...) are still present. The pass prints those operations, +// tagged with the pipeline position it was inserted at. +// +// It is exposed through both plugin entry points. For `flang -fc1 -load`, a +// static initializer calls fir::registerPassPipelineConfigCallback and hooks +// the pass onto the HLFIROptEarly and HLFIROptLast extension points. For +// fir-opt, mlirGetPassPluginInfo makes it available to --load-pass-plugin: +// +// fir-opt --load-pass-plugin=./flangHLFIRPipelinePlugin.so \ +// --pass-pipeline='builtin.module(print-hlfir-intrinsics)' +// +//===----------------------------------------------------------------------===// + +#include "flang/Optimizer/Passes/Pipelines.h" +#include "flang/Tools/CrossToolHelpers.h" + +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" +#include "mlir/Pass/PassRegistry.h" +#include "mlir/Tools/Plugins/PassPlugin.h" + +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/raw_ostream.h" + +namespace { + +/// Print every HLFIR operation still present in the module, tagged with a +/// caller-supplied label. Matches on the `hlfir` dialect namespace rather than +/// a hard-coded op list, so the example does not need to link the HLFIR dialect +/// library. +struct PrintHLFIRIntrinsicsPass + : public mlir::PassWrapper<PrintHLFIRIntrinsicsPass, + mlir::OperationPass<mlir::ModuleOp>> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PrintHLFIRIntrinsicsPass) + + PrintHLFIRIntrinsicsPass() = default; + explicit PrintHLFIRIntrinsicsPass(llvm::StringRef labelValue) { + label = labelValue.str(); + } + PrintHLFIRIntrinsicsPass(const PrintHLFIRIntrinsicsPass &other) + : mlir::PassWrapper<PrintHLFIRIntrinsicsPass, + mlir::OperationPass<mlir::ModuleOp>>(other) { + label = other.label; + } + + llvm::StringRef getArgument() const override { + return "print-hlfir-intrinsics"; + } + llvm::StringRef getDescription() const override { + return "Print the HLFIR operations that are still present in the module"; + } + + Option<std::string> label{*this, "label", + llvm::cl::desc("Tag prefixed to every printed line, identifying the " + "pipeline position this pass was inserted at"), + llvm::cl::init("hlfir")}; + + void runOnOperation() override { + llvm::outs() << "[" << label << "] begin\n"; + getOperation().walk([&](mlir::Operation *op) { + if (op->getName().getDialectNamespace() == "hlfir") { + llvm::outs() << "[" << label << "] " << op->getName().getStringRef() + << "\n"; + } + }); + llvm::outs() << "[" << label << "] end\n"; + } +}; + +struct FlangPipelineRegistration { + FlangPipelineRegistration() { + fir::registerPassPipelineConfigCallback( + [](MLIRToLLVMPassPipelineConfig &config) { + config.registerHLFIROptEarlyEPCallbacks( + [](mlir::PassManager &pm, llvm::OptimizationLevel) { + pm.addPass( + std::make_unique<PrintHLFIRIntrinsicsPass>("hlfir-early")); + }); + config.registerHLFIROptLastEPCallbacks( + [](mlir::PassManager &pm, llvm::OptimizationLevel) { + pm.addPass( + std::make_unique<PrintHLFIRIntrinsicsPass>("hlfir-last")); + }); + }); + } +}; + +// The constructor runs when the shared object is loaded, before any compilation +// starts. +static FlangPipelineRegistration flangPipelineRegistration; + +} // namespace + +/// Entry point used by fir-opt's --load-pass-plugin. +extern "C" LLVM_ATTRIBUTE_WEAK mlir::PassPluginLibraryInfo +mlirGetPassPluginInfo() { + return {MLIR_PLUGIN_API_VERSION, "HLFIRPipelinePlugin", LLVM_VERSION_STRING, + []() { mlir::PassRegistration<PrintHLFIRIntrinsicsPass>(); }}; +} diff --git a/flang/test/CMakeLists.txt b/flang/test/CMakeLists.txt index 50980a521b241..76e5b0809b31b 100644 --- a/flang/test/CMakeLists.txt +++ b/flang/test/CMakeLists.txt @@ -138,6 +138,7 @@ if (LLVM_INCLUDE_EXAMPLES) flangPrintFunctionNames flangOmpReport flangFeatureList + flangHLFIRPipelinePlugin ) endif () diff --git a/flang/test/Examples/fir-opt-pass-plugin.fir b/flang/test/Examples/fir-opt-pass-plugin.fir new file mode 100644 index 0000000000000..f061ac1678f25 --- /dev/null +++ b/flang/test/Examples/fir-opt-pass-plugin.fir @@ -0,0 +1,34 @@ +// Check that fir-opt can load an out-of-tree MLIR pass plugin. This needs +// fir-opt to export its symbols (export_executable_symbols_for_plugins in +// flang/tools/fir-opt/CMakeLists.txt); without them the shared object cannot +// resolve the MLIR and FIR symbols it uses and fails to load. + +// REQUIRES: plugins, examples +// XFAIL: system-aix + +// RUN: fir-opt --load-pass-plugin=%llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ +// RUN: --pass-pipeline='builtin.module(print-hlfir-intrinsics{label=fir-opt})' \ +// RUN: -o /dev/null %s | FileCheck %s + +// The plugin's pass must also show up in fir-opt's pass registry. +// RUN: fir-opt --load-pass-plugin=%llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ +// RUN: --help 2>&1 | FileCheck %s --check-prefix=HELP + +// CHECK: [fir-opt] begin +// CHECK: [fir-opt] hlfir.matmul +// CHECK: [fir-opt] hlfir.sum +// CHECK: [fir-opt] end + +// HELP: --print-hlfir-intrinsics + +func.func @test_hlfir_intrinsics(%a: !fir.ref<!fir.array<3x3xf32>>, + %b: !fir.ref<!fir.array<3x3xf32>>, + %c: !fir.ref<!fir.array<3x3xf32>>, + %s: !fir.ref<f32>) { + %0 = hlfir.matmul %a %b : (!fir.ref<!fir.array<3x3xf32>>, !fir.ref<!fir.array<3x3xf32>>) -> !hlfir.expr<3x3xf32> + hlfir.assign %0 to %c : !hlfir.expr<3x3xf32>, !fir.ref<!fir.array<3x3xf32>> + hlfir.destroy %0 : !hlfir.expr<3x3xf32> + %1 = hlfir.sum %a : (!fir.ref<!fir.array<3x3xf32>>) -> f32 + hlfir.assign %1 to %s : f32, !fir.ref<f32> + return +} diff --git a/flang/test/Examples/hlfir-pipeline-plugin.f90 b/flang/test/Examples/hlfir-pipeline-plugin.f90 new file mode 100644 index 0000000000000..4958a9a9538ac --- /dev/null +++ b/flang/test/Examples/hlfir-pipeline-plugin.f90 @@ -0,0 +1,51 @@ +! Check that a plugin loaded with `flang -fc1 -load` can insert MLIR passes into +! the HLFIR-to-FIR pass pipeline through the HLFIR extension points. + +! REQUIRES: plugins, examples +! XFAIL: system-aix + +! At -O0 no HLFIR simplification runs, so the intrinsics reach both extension +! points. -emit-fir exercises CodeGenAction::lowerHLFIRToFIR. +! RUN: %flang_fc1 -load %llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ +! RUN: -emit-fir -o /dev/null %s 2>&1 | FileCheck %s + +! The same callbacks must fire on the -emit-llvm path, which builds its own +! config in CodeGenAction::generateLLVMIR. +! RUN: %flang_fc1 -load %llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ +! RUN: -emit-llvm -o /dev/null %s 2>&1 | FileCheck %s + +! At -O2 SimplifyHLFIRIntrinsics expands the intrinsics before the Last +! extension point, which is why both extension points exist. +! RUN: %flang_fc1 -load %llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ +! RUN: -O2 -emit-fir -o /dev/null %s 2>&1 | FileCheck %s --check-prefix=O2 + +! Without the plugin nothing is printed. +! RUN: %flang_fc1 -emit-fir -o /dev/null %s 2>&1 \ +! RUN: | FileCheck %s --check-prefix=NOPLUGIN --allow-empty + +! CHECK: [hlfir-early] begin +! CHECK: [hlfir-early] hlfir.matmul +! CHECK: [hlfir-early] hlfir.sum +! CHECK: [hlfir-early] end +! CHECK: [hlfir-last] begin +! CHECK: [hlfir-last] hlfir.matmul +! CHECK: [hlfir-last] hlfir.sum +! CHECK: [hlfir-last] end + +! O2: [hlfir-early] begin +! O2: [hlfir-early] hlfir.matmul +! O2: [hlfir-early] hlfir.sum +! O2: [hlfir-early] end +! O2: [hlfir-last] begin +! O2-NOT: hlfir.matmul +! O2-NOT: hlfir.sum +! O2: [hlfir-last] end + +! NOPLUGIN-NOT: hlfir-early +! NOPLUGIN-NOT: hlfir-last + +subroutine test_hlfir_intrinsics(a, b, c, s) + real :: a(3,3), b(3,3), c(3,3), s + c = matmul(a, b) + s = sum(a) +end subroutine _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
