[flang-commits] [flang] 13034b3 - [flang] Add HLFIR-to-FIR pass pipeline extension points (#212194)

via flang-commits flang-commits at lists.llvm.org
Tue Aug 4 23:54:53 PDT 2026


Author: Valentin Churavy
Date: 2026-08-05T06:54:48Z
New Revision: 13034b3a22058f6bc49553268b051bdc6aa4efa1

URL: https://github.com/llvm/llvm-project/commit/13034b3a22058f6bc49553268b051bdc6aa4efa1
DIFF: https://github.com/llvm/llvm-project/commit/13034b3a22058f6bc49553268b051bdc6aa4efa1.diff

LOG: [flang] Add HLFIR-to-FIR pass pipeline extension points (#212194)

The FIR optimizer extension points (FIROptEarly, FIRInliner, FIROptLast)
all
run after HLFIR has been lowered to FIR, so the HLFIR intrinsic
operations
(hlfir.sum, hlfir.matmul, ...) are gone by the time they run.
Transformations
that need to see those operations have nowhere to attach.

Add two extension points to createHLFIRToFIRPassPipeline:

  * HLFIROptEarly, at the start of the pipeline, before any HLFIR
    simplification or inlining.
  * HLFIROptLast, just before createLowerHLFIRIntrinsics.

Drivers register passes through registerHLFIROptEarlyEPCallbacks and
registerHLFIROptLastEPCallbacks on MLIRToLLVMPassPipelineConfig. The
invoke
methods are const so they can be called on the const config the HLFIR
pipeline
receives.

Co-authored-by: Claude Opus 5 <noreply at anthropic.com>

Added: 
    flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp

Modified: 
    flang/docs/FlangDriver.md
    flang/include/flang/Tools/CrossToolHelpers.h
    flang/lib/Optimizer/Passes/Pipelines.cpp
    flang/unittests/Optimizer/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/flang/docs/FlangDriver.md b/flang/docs/FlangDriver.md
index 21ae600711741..974cbe542855e 100644
--- a/flang/docs/FlangDriver.md
+++ b/flang/docs/FlangDriver.md
@@ -542,6 +542,27 @@ passes at 
diff erent points of the default pass pipeline. An example use of these
 extension point callbacks is shown in `registerDefaultInlinerPass` to invoke the
 default inliner pass in `flang`.
 
+These extension points all run after HLFIR has been lowered to FIR, so the HLFIR
+intrinsic operations (`hlfir.sum`, `hlfir.matmul`, ...) are already gone. For
+transformations that need to see them, `createHLFIRToFIRPassPipeline` provides
+two more extension points:
+
+* `invokeHLFIROptEarlyEPCallbacks` runs at the start of the pipeline, before any
+  HLFIR simplification or inlining.
+* `invokeHLFIROptLastEPCallbacks` runs just before `createLowerHLFIRIntrinsics`,
+  the last point at which HLFIR intrinsic operations still exist.
+
+Drivers register passes with `registerHLFIROptEarlyEPCallbacks` and
+`registerHLFIROptLastEPCallbacks` on the `MLIRToLLVMPassPipelineConfig` (defined
+in `flang/include/flang/Tools/CrossToolHelpers.h`), for example:
+
+```c++
+config.registerHLFIROptEarlyEPCallbacks(
+    [](mlir::PassManager &pm, llvm::OptimizationLevel) {
+      pm.addPass(createMyHLFIRPass());
+    });
+```
+
 ## LLVM Pass Plugins
 
 Pass plugins are dynamic shared objects that consist of one or more LLVM IR

diff  --git a/flang/include/flang/Tools/CrossToolHelpers.h b/flang/include/flang/Tools/CrossToolHelpers.h
index 7bae6e9824795..6569d34e0f255 100644
--- a/flang/include/flang/Tools/CrossToolHelpers.h
+++ b/flang/include/flang/Tools/CrossToolHelpers.h
@@ -46,6 +46,18 @@ class FlangEPCallBacks {
     FIROptLastEPCallbacks.push_back(C);
   }
 
+  void registerHLFIROptEarlyEPCallbacks(
+      const std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>
+          &C) {
+    HLFIROptEarlyEPCallbacks.push_back(C);
+  }
+
+  void registerHLFIROptLastEPCallbacks(
+      const std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>
+          &C) {
+    HLFIROptLastEPCallbacks.push_back(C);
+  }
+
   void invokeFIROptEarlyEPCallbacks(
       mlir::PassManager &pm, llvm::OptimizationLevel optLevel) {
     for (auto &C : FIROptEarlyEPCallbacks)
@@ -64,6 +76,18 @@ class FlangEPCallBacks {
       C(pm, optLevel);
   };
 
+  void invokeHLFIROptEarlyEPCallbacks(
+      mlir::PassManager &pm, llvm::OptimizationLevel optLevel) const {
+    for (auto &C : HLFIROptEarlyEPCallbacks)
+      C(pm, optLevel);
+  };
+
+  void invokeHLFIROptLastEPCallbacks(
+      mlir::PassManager &pm, llvm::OptimizationLevel optLevel) const {
+    for (auto &C : HLFIROptLastEPCallbacks)
+      C(pm, optLevel);
+  };
+
 private:
   llvm::SmallVector<
       std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>, 1>
@@ -76,6 +100,14 @@ class FlangEPCallBacks {
   llvm::SmallVector<
       std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>, 1>
       FIROptLastEPCallbacks;
+
+  llvm::SmallVector<
+      std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>, 1>
+      HLFIROptEarlyEPCallbacks;
+
+  llvm::SmallVector<
+      std::function<void(mlir::PassManager &, llvm::OptimizationLevel)>, 1>
+      HLFIROptLastEPCallbacks;
 };
 
 /// Configuriation for the MLIR to LLVM pass pipeline.

diff  --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index ba346bfb62111..15a342e10fc7f 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -263,6 +263,9 @@ void createHLFIRToFIRPassPipeline(mlir::PassManager &pm,
                                   EnableOpenMP enableOpenMP,
                                   const MLIRToLLVMPassPipelineConfig &config) {
   llvm::OptimizationLevel optLevel = config.OptLevel;
+
+  config.invokeHLFIROptEarlyEPCallbacks(pm, optLevel);
+
   if (optLevel != llvm::OptimizationLevel::O0) {
     addNestedPassToAllTopLevelOperations<PassConstructor>(
         pm, hlfir::createExpressionSimplification);
@@ -310,6 +313,9 @@ void createHLFIRToFIRPassPipeline(mlir::PassManager &pm,
   }
   pm.addPass(hlfir::createLowerHLFIROrderedAssignments(
       {/*tryFusingAssignments=*/optLevel != llvm::OptimizationLevel::O0}));
+
+  config.invokeHLFIROptLastEPCallbacks(pm, optLevel);
+
   pm.addPass(hlfir::createLowerHLFIRIntrinsics());
 
   hlfir::BufferizeHLFIROptions bufferizeOptions;

diff  --git a/flang/unittests/Optimizer/CMakeLists.txt b/flang/unittests/Optimizer/CMakeLists.txt
index 6f83b2ac268da..0e0c12b21bb56 100644
--- a/flang/unittests/Optimizer/CMakeLists.txt
+++ b/flang/unittests/Optimizer/CMakeLists.txt
@@ -19,6 +19,7 @@ set(LIBS
   FIRTransforms
   HLFIRDialect
   MIFDialect
+  flangPasses
 )
 
 add_flang_unittest(FlangOptimizerTests
@@ -44,6 +45,7 @@ add_flang_unittest(FlangOptimizerTests
   FIRContextTest.cpp
   FIRTypesTest.cpp
   FortranVariableTest.cpp
+  HLFIRExtensionPointsTest.cpp
   InternalNamesTest.cpp
   KindMappingTest.cpp
   RTBuilder.cpp

diff  --git a/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp b/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
new file mode 100644
index 0000000000000..035efc1606a3b
--- /dev/null
+++ b/flang/unittests/Optimizer/HLFIRExtensionPointsTest.cpp
@@ -0,0 +1,130 @@
+//===- HLFIRExtensionPointsTest.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for the HLFIR extension points of the HLFIR-to-FIR pass pipeline.
+//
+// The callbacks run when the pipeline is built, so no IR is needed: building
+// the pipeline is enough to observe them.
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/Pass/Pass.h"
+#include "mlir/Pass/PassManager.h"
+#include "flang/Optimizer/Passes/Pipelines.h"
+#include "flang/Tools/CrossToolHelpers.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+#include <vector>
+
+namespace {
+
+struct MarkerPass : public mlir::PassWrapper<MarkerPass,
+                        mlir::OperationPass<mlir::ModuleOp>> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(MarkerPass)
+
+  llvm::StringRef getArgument() const override { return "ep-marker"; }
+  llvm::StringRef getDescription() const override {
+    return "No-op pass used to locate an extension point in a pipeline";
+  }
+  void runOnOperation() override {}
+};
+
+TEST(HLFIRExtensionPoint, CallbacksAreInvokedInOrder) {
+  mlir::MLIRContext context;
+  mlir::PassManager pm(&context, mlir::ModuleOp::getOperationName());
+  MLIRToLLVMPassPipelineConfig config(llvm::OptimizationLevel::O2);
+
+  std::vector<std::string> order;
+  size_t earlySizeAtCall = ~size_t{0};
+
+  config.registerHLFIROptEarlyEPCallbacks(
+      [&](mlir::PassManager &nestedPm, llvm::OptimizationLevel) {
+        order.push_back("early");
+        earlySizeAtCall = nestedPm.size();
+      });
+  config.registerHLFIROptLastEPCallbacks(
+      [&](mlir::PassManager &, llvm::OptimizationLevel) {
+        order.push_back("last");
+      });
+
+  fir::createHLFIRToFIRPassPipeline(pm, fir::EnableOpenMP::None, config);
+
+  ASSERT_EQ(order.size(), 2u);
+  EXPECT_EQ(order[0], "early");
+  EXPECT_EQ(order[1], "last");
+  EXPECT_EQ(earlySizeAtCall, 0u);
+  EXPECT_GT(pm.size(), 0u);
+}
+
+TEST(HLFIRExtensionPoint, CallbacksAreInvokedAtEveryOptLevel) {
+  for (llvm::OptimizationLevel level :
+      {llvm::OptimizationLevel::O0, llvm::OptimizationLevel::O1,
+          llvm::OptimizationLevel::O2, llvm::OptimizationLevel::O3}) {
+    mlir::MLIRContext context;
+    mlir::PassManager pm(&context, mlir::ModuleOp::getOperationName());
+    MLIRToLLVMPassPipelineConfig config(level);
+
+    int earlyCount = 0;
+    int lastCount = 0;
+    llvm::OptimizationLevel seenLevel = llvm::OptimizationLevel::O0;
+    config.registerHLFIROptEarlyEPCallbacks(
+        [&](mlir::PassManager &, llvm::OptimizationLevel cbLevel) {
+          ++earlyCount;
+          seenLevel = cbLevel;
+        });
+    config.registerHLFIROptLastEPCallbacks(
+        [&](mlir::PassManager &, llvm::OptimizationLevel) { ++lastCount; });
+
+    fir::createHLFIRToFIRPassPipeline(pm, fir::EnableOpenMP::None, config);
+
+    EXPECT_EQ(earlyCount, 1);
+    EXPECT_EQ(lastCount, 1);
+    EXPECT_EQ(seenLevel, level);
+  }
+}
+
+TEST(HLFIRExtensionPoint, MarkersAreAtTheDocumentedPositions) {
+  mlir::MLIRContext context;
+  mlir::PassManager pm(&context, mlir::ModuleOp::getOperationName());
+  MLIRToLLVMPassPipelineConfig config(llvm::OptimizationLevel::O2);
+
+  config.registerHLFIROptEarlyEPCallbacks(
+      [](mlir::PassManager &nestedPm, llvm::OptimizationLevel) {
+        nestedPm.addPass(std::make_unique<MarkerPass>());
+      });
+  config.registerHLFIROptLastEPCallbacks(
+      [](mlir::PassManager &nestedPm, llvm::OptimizationLevel) {
+        nestedPm.addPass(std::make_unique<MarkerPass>());
+      });
+
+  fir::createHLFIRToFIRPassPipeline(pm, fir::EnableOpenMP::None, config);
+
+  std::string pipeline;
+  llvm::raw_string_ostream os(pipeline);
+  pm.printAsTextualPipeline(os);
+
+  size_t earlyMarker = pipeline.find("ep-marker");
+  ASSERT_NE(earlyMarker, std::string::npos) << pipeline;
+  size_t lastMarker = pipeline.find("ep-marker", earlyMarker + 1);
+  ASSERT_NE(lastMarker, std::string::npos) << pipeline;
+
+  size_t simplify = pipeline.find("simplify-hlfir-intrinsics");
+  ASSERT_NE(simplify, std::string::npos) << pipeline;
+  size_t lowerIntrinsics = pipeline.find("lower-hlfir-intrinsics");
+  ASSERT_NE(lowerIntrinsics, std::string::npos) << pipeline;
+
+  EXPECT_LT(earlyMarker, simplify) << pipeline;
+  EXPECT_GT(lastMarker, simplify) << pipeline;
+  EXPECT_LT(lastMarker, lowerIntrinsics) << pipeline;
+}
+
+} // namespace


        


More information about the flang-commits mailing list