diff --git a/llvm/examples/CMakeLists.txt b/llvm/examples/CMakeLists.txt --- a/llvm/examples/CMakeLists.txt +++ b/llvm/examples/CMakeLists.txt @@ -8,6 +8,7 @@ add_subdirectory(OrcV2Examples) add_subdirectory(SpeculativeJIT) add_subdirectory(Bye) +add_subdirectory(MachineBye) if(LLVM_ENABLE_EH AND (NOT WIN32) AND (NOT "${LLVM_NATIVE_ARCH}" STREQUAL "ARM")) add_subdirectory(ExceptionDemo) diff --git a/llvm/examples/MachineBye/CMakeLists.txt b/llvm/examples/MachineBye/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/examples/MachineBye/CMakeLists.txt @@ -0,0 +1,19 @@ +# The plugin expects not to link against the Support and Core libraries, +# but expects them to exist in the process loading the plugin. This doesn't +# work with DLLs on Windows (where a shared library can't have undefined +# references), so just skip this example on Windows. +if (NOT WIN32) + # Remove any -zdefs flag (which disallows undefined symbols in + # shared objects) added by the main cmake files on Linux when + # BUILD_SHARED_LIBS is enabled + STRING(REPLACE "-Wl,-z,defs" "" CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS}") + + # We have to build this shared so we can llc -load it + add_llvm_example_library(MachineBye + MachineBye.cpp + MODULE + DEPENDS + intrinsics_gen + ) +endif() diff --git a/llvm/examples/MachineBye/MachineBye.cpp b/llvm/examples/MachineBye/MachineBye.cpp new file mode 100644 --- /dev/null +++ b/llvm/examples/MachineBye/MachineBye.cpp @@ -0,0 +1,37 @@ +// Example showing how to manipulate a MachineFunction using your own pass via +// the TargetPassConfig::addExtension mechanism + +#include "llvm/CodeGen/MachineFunctionPass.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetPassConfig.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/Host.h" +#include "llvm/Target/TargetMachine.h" + +using namespace llvm; + +namespace { +class MachineByePass : public MachineFunctionPass { +public: + static char ID; + bool runOnMachineFunction(MachineFunction &MF) override; + MachineByePass(); +}; + +char MachineByePass::ID = 0; + +MachineByePass::MachineByePass() : MachineFunctionPass(ID) {} + +bool MachineByePass::runOnMachineFunction(MachineFunction &MF) { + errs() << "MachineBye: "; + errs().write_escaped(MF.getName()) << '\n'; + return false; +} + +} // end of anonymous namespace + +static RegisterTargetExtension Registrar(TargetPassConfig::MPEP_EarlyAsPossible, + [](TargetPassConfig &TPC) { + TPC.addPass(new MachineByePass); + }); diff --git a/llvm/include/llvm/CodeGen/TargetPassConfig.h b/llvm/include/llvm/CodeGen/TargetPassConfig.h --- a/llvm/include/llvm/CodeGen/TargetPassConfig.h +++ b/llvm/include/llvm/CodeGen/TargetPassConfig.h @@ -16,10 +16,12 @@ #include "llvm/Pass.h" #include "llvm/Support/CodeGen.h" #include +#include #include namespace llvm { +class Target; class LLVMTargetMachine; struct MachineSchedContext; class PassConfigImpl; @@ -82,6 +84,24 @@ /// This is an ImmutablePass solely for the purpose of exposing CodeGen options /// to the internals of other CodeGen passes. class TargetPassConfig : public ImmutablePass { +public: + /// Enum identifying when in the machine pass setup to add an + /// extension via a callback. + enum MachinePassExtensionPointTy { + MPEP_EarlyAsPossible, + MPEP_PreRegAlloc, + MPEP_PostRegAlloc, + MPEP_PreSched2, + MPEP_PreEmitPass, + MPEP_LateAsPossible + }; + + /// Callback function type for added extensions to a target. + using ExtensionFn = std::function; + + /// ID used for removing a previously added target extension + using ExtensionID = int; + private: PassManagerBase *PM = nullptr; AnalysisID StartBefore = nullptr; @@ -341,6 +361,24 @@ /// Returns the CSEConfig object to use for the current optimization level. virtual std::unique_ptr getCSEConfig() const; + /// Add an extension to be applied to all Targets, returning a + /// nonzero ID for use with removeExtension. The preferred way to + /// register is via the RegisterTargetExtension class defined + /// below. If the extension only applies to particular target(s) the + /// ExtensionFn callback can check the properties of its + /// TargetPassConfig argument, e.g. via TPC.getTM(). + static ExtensionID addExtension(MachinePassExtensionPointTy MPEP, + ExtensionFn Fn); + + /// Remove a previously added extension. The preferred way to use + /// this is via the RegisterTargetExtension class defined below. + static void removeExtension(ExtensionID ID); + + /// Add a pass to the PassManager if that pass is supposed to be run, as + /// determined by the StartAfter and StopAfter options. Takes ownership of the + /// pass. + void addPass(Pass *P); + protected: // Helper to verify the analysis is really immutable. void setOpt(bool &Opt, bool Val); @@ -452,11 +490,6 @@ /// Return the pass that was added, or zero if no pass was added. AnalysisID addPass(AnalysisID PassID); - /// Add a pass to the PassManager if that pass is supposed to be run, as - /// determined by the StartAfter and StopAfter options. Takes ownership of the - /// pass. - void addPass(Pass *P); - /// addMachinePasses helper to create the target-selected or overriden /// regalloc pass. virtual FunctionPass *createRegAllocPass(bool Optimized); @@ -465,11 +498,41 @@ /// and rewriting. \returns true if any passes were added. virtual bool addRegAssignAndRewriteFast(); virtual bool addRegAssignAndRewriteOptimized(); + +private: + /// Scan the set of extensions and call any registered for our + /// Target at the given extension point. + void applyAnyExtensions(MachinePassExtensionPointTy MPEP); }; void registerCodeGenCallback(PassInstrumentationCallbacks &PIC, LLVMTargetMachine &); +/// Registers and de-registers a target extension function +class RegisterTargetExtension { + TargetPassConfig::ExtensionID ExtensionID; + +public: + RegisterTargetExtension(TargetPassConfig::MachinePassExtensionPointTy MPEP, + TargetPassConfig::ExtensionFn Fn) + : ExtensionID(TargetPassConfig::addExtension(MPEP, std::move(Fn))) {} + + ~RegisterTargetExtension() { + if (ExtensionID) + TargetPassConfig::removeExtension(ExtensionID); + } + + // Movable but not copyable + RegisterTargetExtension(RegisterTargetExtension &&Other) + : ExtensionID(Other.ExtensionID) { + Other.ExtensionID = 0; + } + + RegisterTargetExtension(const RegisterTargetExtension &) = delete; + RegisterTargetExtension &operator=(const RegisterTargetExtension &) = delete; + RegisterTargetExtension &operator=(RegisterTargetExtension &&) = delete; +}; + } // end namespace llvm #endif // LLVM_CODEGEN_TARGETPASSCONFIG_H diff --git a/llvm/include/llvm/Target/TargetMachine.h b/llvm/include/llvm/Target/TargetMachine.h --- a/llvm/include/llvm/Target/TargetMachine.h +++ b/llvm/include/llvm/Target/TargetMachine.h @@ -34,6 +34,7 @@ class Function; class GlobalValue; +class LLVMTargetMachine; class MachineFunctionPassManager; class MachineFunctionAnalysisManager; class MachineModuleInfoWrapperPass; @@ -166,6 +167,15 @@ return false; } + /// If this is an LLVMTargetMachine then return the downcast + /// pointer, otherwise return nullptr. + virtual LLVMTargetMachine *asLLVMTargetMachine() { return nullptr; } + + /// Const version of asLLVMTargetMachine + const LLVMTargetMachine *asLLVMTargetMachine() const { + return const_cast(this)->asLLVMTargetMachine(); + } + /// This method returns a pointer to the specified type of /// TargetSubtargetInfo. In debug builds, it verifies that the object being /// returned is of the correct type. @@ -426,6 +436,9 @@ void initAsmInfo(); public: + /// Provide safe downcast + LLVMTargetMachine *asLLVMTargetMachine() override { return this; } + /// Get a TargetTransformInfo implementation for the target. /// /// The TTI returned uses the common code generator to answer queries about diff --git a/llvm/lib/CodeGen/TargetPassConfig.cpp b/llvm/lib/CodeGen/TargetPassConfig.cpp --- a/llvm/lib/CodeGen/TargetPassConfig.cpp +++ b/llvm/lib/CodeGen/TargetPassConfig.cpp @@ -40,6 +40,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Discriminator.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/ManagedStatic.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/Threading.h" #include "llvm/Support/VirtualFileSystem.h" @@ -51,6 +52,7 @@ #include #include #include +#include using namespace llvm; @@ -250,6 +252,12 @@ "disable-select-optimize", cl::init(true), cl::Hidden, cl::desc("Disable the select-optimization pass from running")); +using GlobalExtensionTy = + std::tuple; + +static ManagedStatic> GlobalExtensions; + /// Allow standard passes to be disabled by command line options. This supports /// simple binary flags that either suppress the pass or do nothing. /// i.e. -disable-mypass=false has no effect. @@ -711,6 +719,35 @@ FinalPtr.getID() != ID; } +auto TargetPassConfig::addExtension(MachinePassExtensionPointTy MPEP, + ExtensionFn Fn) -> ExtensionID { + static ExtensionID Counter = 0; + ExtensionID ID = ++Counter; + GlobalExtensions->push_back(std::make_tuple(MPEP, Fn, ID)); + return ID; +} + +void TargetPassConfig::removeExtension(ExtensionID ID) { + if (!GlobalExtensions.isConstructed()) + return; + + auto *I = llvm::find_if(*GlobalExtensions, [ID](const auto &Elem) { + return std::get<2>(Elem) == ID; + }); + + if (I != GlobalExtensions->end()) + GlobalExtensions->erase(I); +} + +void TargetPassConfig::applyAnyExtensions(MachinePassExtensionPointTy MPEP) { + if (GlobalExtensions.isConstructed() && !GlobalExtensions->empty()) { + for (const auto &Ext : *GlobalExtensions) { + if (std::get<0>(Ext) == MPEP) + std::get<1>(Ext)(*this); + } + } +} + /// Add a pass to the PassManager if that pass is supposed to be run. If the /// Started/Stopped flags indicate either that the compilation should start at /// a later pass or that it should stop after an earlier pass, then do not add @@ -1118,11 +1155,16 @@ /// tied to a common pass. But if it has subtle dependencies on multiple passes, /// the target should override the stage instead. /// +/// External code can customize the passes added here for any target +/// via the addGlobalExtension function. +/// /// TODO: We could use a single addPre/Post(ID) hook to allow pass injection /// before/after any target-independent pass. But it's currently overkill. void TargetPassConfig::addMachinePasses() { AddingMachinePasses = true; + applyAnyExtensions(MPEP_EarlyAsPossible); + // Add passes that optimize machine instructions in SSA form. if (getOptLevel() != CodeGenOpt::None) { addMachineSSAOptimization(); @@ -1137,6 +1179,7 @@ // Run pre-ra passes. addPreRegAlloc(); + applyAnyExtensions(MPEP_PreRegAlloc); // Debugifying the register allocator passes seems to provoke some // non-determinism that affects CodeGen and there doesn't seem to be a point @@ -1163,6 +1206,7 @@ addFastRegAlloc(); // Run post-ra passes. + applyAnyExtensions(MPEP_PostRegAlloc); addPostRegAlloc(); addPass(&RemoveRedundantDebugValuesID); @@ -1189,6 +1233,7 @@ // Run pre-sched2 passes. addPreSched2(); + applyAnyExtensions(MPEP_PreSched2); if (EnableImplicitNullChecks) addPass(&ImplicitNullChecksID); @@ -1221,6 +1266,7 @@ addPass(&PatchableFunctionID); addPreEmitPass(); + applyAnyExtensions(MPEP_PreEmitPass); if (TM->Options.EnableIPRA) // Collect register usage information and produce a register mask of @@ -1286,6 +1332,8 @@ // Add passes that directly emit MI after all other MI passes. addPreEmitPass2(); + applyAnyExtensions(MPEP_LateAsPossible); + AddingMachinePasses = false; } diff --git a/llvm/test/CMakeLists.txt b/llvm/test/CMakeLists.txt --- a/llvm/test/CMakeLists.txt +++ b/llvm/test/CMakeLists.txt @@ -190,6 +190,7 @@ list(APPEND LLVM_TEST_DEPENDS Bye ExampleIRTransforms + MachineBye ) endif() endif() diff --git a/llvm/test/Feature/machine_bye.ll b/llvm/test/Feature/machine_bye.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Feature/machine_bye.ll @@ -0,0 +1,10 @@ +; RUN: llc < %s 2>&1 | FileCheck %s --check-prefixes BOTH +; RUN: llc %loadmachinebye < %s 2>&1 | FileCheck %s --check-prefixes BOTH,LOAD +; REQUIRES: examples +; UNSUPPORTED: system-windows + +; LOAD: MachineBye: +; BOTH-LABEL: foo +define void @foo() { + ret void +} diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -373,6 +373,9 @@ if config.build_examples: config.available_features.add("examples") + config.substitutions.append(("%loadmachinebye", + "-load={}/MachineBye{}".format(config.llvm_shlib_dir, + config.llvm_shlib_ext))) if config.linked_bye_extension: config.substitutions.append(("%llvmcheckext", "CHECK-EXT")) diff --git a/llvm/unittests/CodeGen/CMakeLists.txt b/llvm/unittests/CodeGen/CMakeLists.txt --- a/llvm/unittests/CodeGen/CMakeLists.txt +++ b/llvm/unittests/CodeGen/CMakeLists.txt @@ -38,6 +38,7 @@ SchedBoundary.cpp SelectionDAGAddressAnalysisTest.cpp TypeTraitsTest.cpp + TargetExtensionTest.cpp TargetOptionsTest.cpp TestAsmPrinter.cpp MLRegallocDevelopmentFeatures.cpp diff --git a/llvm/unittests/CodeGen/TargetExtensionTest.cpp b/llvm/unittests/CodeGen/TargetExtensionTest.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/CodeGen/TargetExtensionTest.cpp @@ -0,0 +1,86 @@ +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetPassConfig.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/IR/Module.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Target/TargetOptions.h" +#include "llvm/TargetParser/Host.h" +#include "gtest/gtest.h" +#include +#include + +using namespace llvm; + +static void addMachinePasses(LLVMTargetMachine *LLVMTM) { + legacy::PassManager PM; + TargetPassConfig *TPC(LLVMTM->createPassConfig(PM)); + + auto MMIWMP = std::make_unique(LLVMTM); + PM.add(TPC); + PM.add(MMIWMP.release()); + TPC->addMachinePasses(); +} + +TEST(TargetExtensionTest, ExtensionOrdering) { + InitializeNativeTarget(); + + LLVMContext Context; + + Module Mod("target-extensions-test", Context); + Mod.setTargetTriple(llvm::sys::getDefaultTargetTriple()); + std::string Error; + const Target *Target( + TargetRegistry::lookupTarget(Mod.getTargetTriple(), Error)); + + ASSERT_NE(Target, nullptr); + std::unique_ptr TM(Target->createTargetMachine( + Mod.getTargetTriple(), "", "", TargetOptions{}, std::nullopt)); + ASSERT_NE(TM.get(), nullptr); + LLVMTargetMachine *LTM(TM->asLLVMTargetMachine()); + + if (LTM) { + // Register extensions at all possible points + unsigned CallCount = + static_cast(TargetPassConfig::MPEP_EarlyAsPossible); + std::vector Registrars; + + for (unsigned MPEP = TargetPassConfig::MPEP_EarlyAsPossible; + MPEP <= TargetPassConfig::MPEP_LateAsPossible; ++MPEP) { + + // Register an extension at each extension point for this machine + // to allow checks for any enums we missed in the implementation + // and that they are called in the correct order + Registrars.emplace_back( + static_cast(MPEP), + [MPEP, &CallCount, &Mod](TargetPassConfig &TPC) { + if (TPC.getTM().getTargetTriple().str() == + Mod.getTargetTriple()) { + // Check that we are called in expected order for the machine + EXPECT_EQ(CallCount, MPEP) << "Calls arrived out of order"; + ++CallCount; + } + }); + } + + EXPECT_EQ(CallCount, + static_cast(TargetPassConfig::MPEP_EarlyAsPossible)) + << "Initial callcount mismatch"; + + // Check that all registered extension points get called + addMachinePasses(LTM); + + EXPECT_EQ(CallCount, + static_cast(TargetPassConfig::MPEP_LateAsPossible) + 1) + << "Check1 callcount mismatch"; + + // Now remove all extensions and check they are not called again + Registrars.clear(); + addMachinePasses(LTM); + + EXPECT_EQ(CallCount, + static_cast(TargetPassConfig::MPEP_LateAsPossible) + 1) + << "Final callcount mismatch"; + } +}