Skip to content

[CodeGen] Expose the extensibility of PassConfig to plugins #139059

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Tcc100
Copy link

@Tcc100 Tcc100 commented May 8, 2025

This PR exposes the backend pass config to plugins via a callback. Plugin authors can register a callback that is being triggered before the target backend adds their passes to the pipeline. In the callback they then get access to the TargetMachine, the PassManager, and the TargetPassConfig. This allows plugins to call TargetPassConfig::insertPass, which is honored in the subsequent addPass of the main backend. We implemented this using the legacy pass manager as the backend is still using the old pass manager.

The following example shows how plugin authors can use the callback. Since its a callback that is not doing anything without anybody registering it, there shouldn't be any potential harm to the compiler unless a plugin is present.

__attribute__((constructor)) static void initCodeGenPlugin() {
    initializeCodeGenTestPass(*PassRegistry::getPassRegistry());

    TargetMachine::registerTargetPassConfigCallback([](auto &TM, auto &PM, auto *TPC) {
        TPC->insertPass(&GCLoweringID, &CodeGenTest::ID);
    });
}

Copy link

github-actions bot commented May 8, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@Tcc100
Copy link
Author

Tcc100 commented May 8, 2025

@nikic @aeubanks please have a look

Copy link
Contributor

@antoniofrighetto antoniofrighetto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a test for this?

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

insertPass is a bad API (and probably going away in the new PM?). Should probably have a set of insert points like the new pass manager does for middle end passes. Also, needs tests / a sample and documentation

@@ -72,6 +74,10 @@ namespace yaml {
struct MachineFunctionInfo;
} // namespace yaml

class TargetMachine;
using PassConfigCallback =
std::function<void(TargetMachine &, PassManagerBase &, TargetPassConfig *)>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function_ref?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a owning reference, similar to the other callback vectors.

@@ -119,6 +119,9 @@ addPassesToGenerateCode(CodeGenTargetMachineImpl &TM, PassManagerBase &PM,
PM.add(PassConfig);
PM.add(&MMIWP);

for (auto& C : *TargetMachine::TargetPassConfigCallbacks)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no auto (and no &?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is same syntax as most of the callbacks in the code base use. Do you think not using auto would be better in this case?

@llvmbot llvmbot added the clang Clang issues not falling into any other category label May 20, 2025
@llvmbot
Copy link
Member

llvmbot commented May 20, 2025

@llvm/pr-subscribers-clang

Author: None (Tcc100)

Changes

This PR exposes the backend pass config to plugins via a callback. Plugin authors can register a callback that is being triggered before the target backend adds their passes to the pipeline. In the callback they then get access to the TargetMachine, the PassManager, and the TargetPassConfig. This allows plugins to call TargetPassConfig::insertPass, which is honored in the subsequent addPass of the main backend. We implemented this using the legacy pass manager as the backend is still using the old pass manager.

The following example shows how plugin authors can use the callback. Since its a callback that is not doing anything without anybody registering it, there shouldn't be any potential harm to the compiler unless a plugin is present.

__attribute__((constructor)) static void initCodeGenPlugin() {
    initializeCodeGenTestPass(*PassRegistry::getPassRegistry());

    TargetMachine::registerTargetPassConfigCallback([](auto &amp;TM, auto &amp;PM, auto *TPC) {
        TPC-&gt;insertPass(&amp;GCLoweringID, &amp;CodeGenTest::ID);
    });
}

Full diff: https://github.com/llvm/llvm-project/pull/139059.diff

5 Files Affected:

  • (added) clang/test/CodeGen/passconfighook.cpp (+56)
  • (modified) llvm/docs/WritingAnLLVMPass.rst (+17)
  • (modified) llvm/include/llvm/Target/TargetMachine.h (+14)
  • (modified) llvm/lib/CodeGen/CodeGenTargetMachineImpl.cpp (+3)
  • (modified) llvm/lib/Target/TargetMachine.cpp (+3)
diff --git a/clang/test/CodeGen/passconfighook.cpp b/clang/test/CodeGen/passconfighook.cpp
new file mode 100644
index 0000000000000..094df123f3de1
--- /dev/null
+++ b/clang/test/CodeGen/passconfighook.cpp
@@ -0,0 +1,56 @@
+// RUN: %clangxx -shared -fPIC -I??/install/include -L%llvmshlibdir %s -o %t.so
+// RUN: %clangxx -O3 -DMAIN -Xclang -load -Xclang %t.so %s -o %t-main | FileCheck %s
+
+#ifndef MAIN
+
+#include <llvm/Target/TargetMachine.h>
+#include <llvm/CodeGen/TargetPassConfig.h>
+#include <llvm/CodeGen/MachineFunctionPass.h>
+#include <llvm/CodeGen/Passes.h>
+
+#define DEBUG_TYPE "codegen-test"
+#define CODEGEN_TEST_NAME "CodeGen Test Pass"
+
+using namespace llvm;
+
+namespace llvm {
+    void initializeCodeGenTestPass(PassRegistry &);
+} // namespace llvm
+
+class CodeGenTest : public MachineFunctionPass {
+public:
+    static char ID;
+
+    CodeGenTest(): MachineFunctionPass(ID) {
+    }
+
+    bool runOnMachineFunction(MachineFunction &MF) override {
+        outs() << "[CodeGen] CodeGenTest::runOnMachineFunction" << "\n";
+        return true;
+    }
+
+    StringRef getPassName() const override {
+        return CODEGEN_TEST_NAME;
+    }
+};
+
+char CodeGenTest::ID = 0;
+INITIALIZE_PASS(CodeGenTest, DEBUG_TYPE, CODEGEN_TEST_NAME, false, false)
+
+__attribute__((constructor)) static void initCodeGenPlugin() {
+    initializeCodeGenTestPass(*PassRegistry::getPassRegistry());
+
+    TargetMachine::registerTargetPassConfigCallback([](auto &TM, auto &PM, auto *TPC) {
+        outs() << "registerTargetPassConfigCallback\n";
+        TPC->insertPass(&GCLoweringID, &CodeGenTest::ID);
+    });
+}
+
+#else
+
+// CHECK: CodeGenTest::runOnMachineFunction
+int main(int argc, char **argv) {
+    return 0;
+}
+
+#endif
diff --git a/llvm/docs/WritingAnLLVMPass.rst b/llvm/docs/WritingAnLLVMPass.rst
index 484227bac38b5..770f5f6acd115 100644
--- a/llvm/docs/WritingAnLLVMPass.rst
+++ b/llvm/docs/WritingAnLLVMPass.rst
@@ -442,6 +442,23 @@ in certain circumstances (such as calling the ``Pass::dump()`` from a
 debugger), so it should only be used to enhance debug output, it should not be
 depended on.
 
+Scheduling a MachineFunctionPass
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Backends create a ``TargetPassConfig`` and use ``addPass`` to schedule
+``MachineFunctionPass``\ es. External plugins can register a callback to modify
+and insert additional passes:
+
+.. code-block:: c++
+
+  TargetMachine::registerTargetPassConfigCallback(
+    [](TargetMachine &TM, PassManager &PM, TargetPassConfig *TPC) {
+      TPC->insertPass(/* ... */);
+      TPC->substitutePass(/* ... */);
+    }
+  );
+
+
 .. _writing-an-llvm-pass-interaction:
 
 Specifying interactions between passes
diff --git a/llvm/include/llvm/Target/TargetMachine.h b/llvm/include/llvm/Target/TargetMachine.h
index 906926729ed74..bcc1ce29b8282 100644
--- a/llvm/include/llvm/Target/TargetMachine.h
+++ b/llvm/include/llvm/Target/TargetMachine.h
@@ -20,10 +20,12 @@
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Error.h"
+#include "llvm/Support/ManagedStatic.h"
 #include "llvm/Support/PGOOptions.h"
 #include "llvm/Target/CGPassBuilderOption.h"
 #include "llvm/Target/TargetOptions.h"
 #include "llvm/TargetParser/Triple.h"
+#include <functional>
 #include <optional>
 #include <string>
 #include <utility>
@@ -72,6 +74,10 @@ namespace yaml {
 struct MachineFunctionInfo;
 } // namespace yaml
 
+class TargetMachine;
+using PassConfigCallback =
+    std::function<void(TargetMachine &, PassManagerBase &, TargetPassConfig *)>;
+
 //===----------------------------------------------------------------------===//
 ///
 /// Primary interface to the complete machine description for the target
@@ -119,6 +125,9 @@ class TargetMachine {
   std::optional<PGOOptions> PGOOption;
 
 public:
+  static ManagedStatic<SmallVector<PassConfigCallback, 1>>
+      TargetPassConfigCallbacks;
+
   mutable TargetOptions Options;
 
   TargetMachine(const TargetMachine &) = delete;
@@ -518,6 +527,11 @@ class TargetMachine {
 
   // MachineRegisterInfo callback function
   virtual void registerMachineRegisterInfoCallback(MachineFunction &MF) const {}
+
+  // TargetPassConfig callback function
+  static void registerTargetPassConfigCallback(const PassConfigCallback &C) {
+    TargetPassConfigCallbacks->push_back(C);
+  }
 };
 
 } // end namespace llvm
diff --git a/llvm/lib/CodeGen/CodeGenTargetMachineImpl.cpp b/llvm/lib/CodeGen/CodeGenTargetMachineImpl.cpp
index 4a3503a2da7db..336f1db776036 100644
--- a/llvm/lib/CodeGen/CodeGenTargetMachineImpl.cpp
+++ b/llvm/lib/CodeGen/CodeGenTargetMachineImpl.cpp
@@ -119,6 +119,9 @@ addPassesToGenerateCode(CodeGenTargetMachineImpl &TM, PassManagerBase &PM,
   PM.add(PassConfig);
   PM.add(&MMIWP);
 
+  for (auto& C : *TargetMachine::TargetPassConfigCallbacks)
+    C(TM, PM, PassConfig);
+
   if (PassConfig->addISelPasses())
     return nullptr;
   PassConfig->addMachinePasses();
diff --git a/llvm/lib/Target/TargetMachine.cpp b/llvm/lib/Target/TargetMachine.cpp
index 69b6e26e602f6..c43e2ba00f733 100644
--- a/llvm/lib/Target/TargetMachine.cpp
+++ b/llvm/lib/Target/TargetMachine.cpp
@@ -332,3 +332,6 @@ std::pair<int, int> TargetMachine::parseBinutilsVersion(StringRef Version) {
     Version.consumeInteger(10, Ret.second);
   return Ret;
 }
+
+// TargetPassConfig callbacks
+ManagedStatic<SmallVector<PassConfigCallback, 1>> TargetMachine::TargetPassConfigCallbacks{};

@Tcc100
Copy link
Author

Tcc100 commented May 20, 2025

I added a test case and some docs, which tuned out to be more challenging than expected, since I couldn't find any test case for the plugin system. The lit test in this PR requires the current llvm headers, which are not provided by the lit system, thus, it currently only works if you provide the absolute path in your system. I'd appreciate any pointers on how to implement this correctly.

@arsenm
Copy link
Contributor

arsenm commented May 21, 2025

I don't think you can reasonably test this with a lit test, you have to add this as a proper build target in llvm/examples similar to Bye

@arsenm
Copy link
Contributor

arsenm commented May 21, 2025

I don't think you can reasonably test this with a lit test, you have to add this as a proper build target in llvm/examples similar to Bye.

A lit test could then drive the load of that plugin, but the lit test can't contain the pass itself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category llvm:codegen
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants