https://github.com/vchuravy updated 
https://github.com/llvm/llvm-project/pull/212196

>From da46b327a32317665294700c6f0ec238eed3279d 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 config augmentor registry and fir-opt's symbol
export 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 there was no worked example of 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. Removing the fir-opt export drops it from ~105k
exported dynamic symbols to one and makes --load-pass-plugin fail to load.

Two tests use it. Examples/hlfir-pipeline-plugin.f90 covers -emit-fir and
-emit-llvm, plus a no-plugin negative check; at -O0 the intrinsics reach both
extension points, while at -O2 SimplifyHLFIRIntrinsics has expanded them before
the Last one, which is why both 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                   | 115 ++++++++++++++++++
 flang/test/CMakeLists.txt                     |   1 +
 flang/test/Examples/fir-opt-pass-plugin.fir   |  36 ++++++
 flang/test/Examples/hlfir-pipeline-plugin.f90 |  53 ++++++++
 7 files changed, 238 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 55b4534cbffbe..f2d6bf06c42ab 100644
--- a/flang/docs/FlangDriver.md
+++ b/flang/docs/FlangDriver.md
@@ -583,6 +583,23 @@ to be populated from static initializers, which run before 
any compilation
 begins. Tools that build the same pipelines without invoking the registry (for
 example `bbc` and `tco`) are unaffected.
 
+`flang/examples/HLFIRPipelinePlugin` is a complete working example, exercised 
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`,
+this requires the tool to export its symbols so that the plugin can resolve the
+MLIR, FIR and HLFIR symbols it uses against the host
+(`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..3cf00a9641e34
--- /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 here on purpose: the 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..3bdf10ce0fc72
--- /dev/null
+++ b/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp
@@ -0,0 +1,115 @@
+//===-- 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 showing how an out-of-tree MLIR pass can be added to flang's
+// HLFIR-to-FIR pass pipeline, while the HLFIR intrinsic operations (hlfir.sum,
+// hlfir.matmul, ...) are still present. It contributes a pass that prints 
those
+// operations, tagged with the pipeline position it was inserted at.
+//
+// The pass is exposed through both plugin entry points. For `flang -fc1 -load`
+// a static initializer calls fir::registerPassPipelineConfigCallback to reach
+// the config the frontend builds, and registers the pass at 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)'
+//
+// Nothing is linked into the shared object: MLIR, FIR and flang symbols 
resolve
+// against the host tool.
+//
+//===----------------------------------------------------------------------===//
+
+#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 identifying the pipeline position. Reports on the
+/// whole `hlfir` dialect rather than a hard-coded op list, so that 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 printed with every 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";
+  }
+};
+
+/// Registers the pass at both HLFIR extension points.
+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"));
+              });
+        });
+  }
+};
+
+// Runs when the shared object is dlopen'd, 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..47e5016d1a203
--- /dev/null
+++ b/flang/test/Examples/fir-opt-pass-plugin.fir
@@ -0,0 +1,36 @@
+// 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 it the shared object cannot
+// resolve the MLIR/FIR symbols it uses and dlopen fails.
+// This requires that the examples are built (LLVM_INCLUDE_EXAMPLES=ON) to
+// access flangHLFIRPipelinePlugin.so
+
+// 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..0bbeefb6c9880
--- /dev/null
+++ b/flang/test/Examples/hlfir-pipeline-plugin.f90
@@ -0,0 +1,53 @@
+! Check that a `flang -fc1 -load`'ed plugin can insert MLIR passes into the
+! HLFIR-to-FIR pass pipeline through the HLFIR extension points.
+! This requires that the examples are built (LLVM_INCLUDE_EXAMPLES=ON) to 
access
+! flangHLFIRPipelinePlugin.so
+
+! 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 has expanded 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

Reply via email to