https://github.com/vchuravy updated https://github.com/llvm/llvm-project/pull/212196
>From 323a51dd33b10fd8fdfe3355e9fa0c29981d220c Mon Sep 17 00:00:00 2001 From: Valentin Churavy <[email protected]> Date: Sun, 26 Jul 2026 23:53:30 +0200 Subject: [PATCH 1/2] [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 d9ecbbc1372bf..3568965b9b770 100644 --- a/flang/docs/FlangDriver.md +++ b/flang/docs/FlangDriver.md @@ -590,6 +590,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 >From 7466b2fff494d83d45ae7976bde5bdc626dc79c5 Mon Sep 17 00:00:00 2001 From: Valentin Churavy <[email protected]> Date: Wed, 30 Sep 2026 12:10:06 +0200 Subject: [PATCH 2/2] [flang] Address review comments on the HLFIR pipeline example plugin - Rename the pass to print-hlfir-ops: it prints every hlfir op, not only the intrinsics. - Drop the redundant option copy in the copy constructor; Pass::clone copies option values. - Tests: drop the no-plugin run, which could not fail, and at -O2 only check that both extension points are reached rather than what SimplifyHLFIRIntrinsics chose to expand. Assisted-by: Claude Code (Opus 5.5) --- .../HLFIRPipelinePlugin.cpp | 41 ++++++++----------- flang/test/Examples/fir-opt-pass-plugin.fir | 4 +- flang/test/Examples/hlfir-pipeline-plugin.f90 | 12 +----- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp b/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp index be93de897cdf8..283d069ceac29 100644 --- a/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp +++ b/flang/examples/HLFIRPipelinePlugin/HLFIRPipelinePlugin.cpp @@ -7,9 +7,10 @@ //===----------------------------------------------------------------------===// // // 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. +// pipeline, at the points where the module is still in HLFIR, including the +// intrinsic operations (hlfir.sum, hlfir.matmul, ...). The pass prints the +// HLFIR operations it finds, 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 @@ -17,7 +18,7 @@ // fir-opt, mlirGetPassPluginInfo makes it available to --load-pass-plugin: // // fir-opt --load-pass-plugin=./flangHLFIRPipelinePlugin.so \ -// --pass-pipeline='builtin.module(print-hlfir-intrinsics)' +// --pass-pipeline='builtin.module(print-hlfir-ops)' // //===----------------------------------------------------------------------===// @@ -40,24 +41,20 @@ namespace { /// 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) +struct PrintHLFIROpsPass : public mlir::PassWrapper<PrintHLFIROpsPass, + mlir::OperationPass<mlir::ModuleOp>> { + MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(PrintHLFIROpsPass) - PrintHLFIRIntrinsicsPass() = default; - explicit PrintHLFIRIntrinsicsPass(llvm::StringRef labelValue) { + PrintHLFIROpsPass() = default; + explicit PrintHLFIROpsPass(llvm::StringRef labelValue) { label = labelValue.str(); } - PrintHLFIRIntrinsicsPass(const PrintHLFIRIntrinsicsPass &other) - : mlir::PassWrapper<PrintHLFIRIntrinsicsPass, - mlir::OperationPass<mlir::ModuleOp>>(other) { - label = other.label; - } + // Options are not copyable; Pass::clone copies their values afterwards. + PrintHLFIROpsPass(const PrintHLFIROpsPass &other) + : mlir::PassWrapper<PrintHLFIROpsPass, + mlir::OperationPass<mlir::ModuleOp>>(other) {} - llvm::StringRef getArgument() const override { - return "print-hlfir-intrinsics"; - } + llvm::StringRef getArgument() const override { return "print-hlfir-ops"; } llvm::StringRef getDescription() const override { return "Print the HLFIR operations that are still present in the module"; } @@ -85,13 +82,11 @@ struct FlangPipelineRegistration { [](MLIRToLLVMPassPipelineConfig &config) { config.registerHLFIROptEarlyEPCallbacks( [](mlir::PassManager &pm, llvm::OptimizationLevel) { - pm.addPass( - std::make_unique<PrintHLFIRIntrinsicsPass>("hlfir-early")); + pm.addPass(std::make_unique<PrintHLFIROpsPass>("hlfir-early")); }); config.registerHLFIROptLastEPCallbacks( [](mlir::PassManager &pm, llvm::OptimizationLevel) { - pm.addPass( - std::make_unique<PrintHLFIRIntrinsicsPass>("hlfir-last")); + pm.addPass(std::make_unique<PrintHLFIROpsPass>("hlfir-last")); }); }); } @@ -107,5 +102,5 @@ static FlangPipelineRegistration flangPipelineRegistration; extern "C" LLVM_ATTRIBUTE_WEAK mlir::PassPluginLibraryInfo mlirGetPassPluginInfo() { return {MLIR_PLUGIN_API_VERSION, "HLFIRPipelinePlugin", LLVM_VERSION_STRING, - []() { mlir::PassRegistration<PrintHLFIRIntrinsicsPass>(); }}; + []() { mlir::PassRegistration<PrintHLFIROpsPass>(); }}; } diff --git a/flang/test/Examples/fir-opt-pass-plugin.fir b/flang/test/Examples/fir-opt-pass-plugin.fir index f061ac1678f25..248c19210c6b5 100644 --- a/flang/test/Examples/fir-opt-pass-plugin.fir +++ b/flang/test/Examples/fir-opt-pass-plugin.fir @@ -7,7 +7,7 @@ // XFAIL: system-aix // RUN: fir-opt --load-pass-plugin=%llvmshlibdir/flangHLFIRPipelinePlugin%pluginext \ -// RUN: --pass-pipeline='builtin.module(print-hlfir-intrinsics{label=fir-opt})' \ +// RUN: --pass-pipeline='builtin.module(print-hlfir-ops{label=fir-opt})' \ // RUN: -o /dev/null %s | FileCheck %s // The plugin's pass must also show up in fir-opt's pass registry. @@ -19,7 +19,7 @@ // CHECK: [fir-opt] hlfir.sum // CHECK: [fir-opt] end -// HELP: --print-hlfir-intrinsics +// HELP: --print-hlfir-ops func.func @test_hlfir_intrinsics(%a: !fir.ref<!fir.array<3x3xf32>>, %b: !fir.ref<!fir.array<3x3xf32>>, diff --git a/flang/test/Examples/hlfir-pipeline-plugin.f90 b/flang/test/Examples/hlfir-pipeline-plugin.f90 index 4958a9a9538ac..1051f2a112694 100644 --- a/flang/test/Examples/hlfir-pipeline-plugin.f90 +++ b/flang/test/Examples/hlfir-pipeline-plugin.f90 @@ -14,15 +14,10 @@ ! 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. +! Both extension points are also reached with the optimizing pipeline. ! 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 @@ -37,13 +32,8 @@ ! 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) _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
