diff --git a/llvm/examples/Bye/CMakeLists.txt b/llvm/examples/Bye/CMakeLists.txt --- a/llvm/examples/Bye/CMakeLists.txt +++ b/llvm/examples/Bye/CMakeLists.txt @@ -7,6 +7,12 @@ # 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}") + add_llvm_pass_plugin(Bye Bye.cpp DEPENDS 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(MachineExtension) if(LLVM_ENABLE_EH AND (NOT WIN32) AND (NOT "${LLVM_NATIVE_ARCH}" STREQUAL "ARM")) add_subdirectory(ExceptionDemo) diff --git a/llvm/examples/MachineExtension/CMakeLists.txt b/llvm/examples/MachineExtension/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/examples/MachineExtension/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(MachineExtension + MachineExtension.cpp + MODULE + DEPENDS + intrinsics_gen + ) +endif() diff --git a/llvm/examples/MachineExtension/MachineExtension.cpp b/llvm/examples/MachineExtension/MachineExtension.cpp new file mode 100644 --- /dev/null +++ b/llvm/examples/MachineExtension/MachineExtension.cpp @@ -0,0 +1,62 @@ +// 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/Support/TargetRegistry.h" +#include "llvm/Target/TargetMachine.h" + +using namespace llvm; + +namespace { +class ExampleMachinePass : public MachineFunctionPass { +public: + static char ID; + static void selfRegister(TargetPassConfig &TPC); + bool runOnMachineFunction(MachineFunction &MF) override; + ExampleMachinePass(); +}; + +char ExampleMachinePass::ID = 0; + +ExampleMachinePass::ExampleMachinePass() : MachineFunctionPass(ID) {} + +void ExampleMachinePass::selfRegister(TargetPassConfig &TPC) { + TPC.addPass(new ExampleMachinePass); +} + +bool ExampleMachinePass::runOnMachineFunction(MachineFunction &MF) { + // This example shows deletion of calls to a supposed "_dummy_intrinsic" + // function. + SmallVector Erasures; + + for (MachineBasicBlock &Block : MF) { + for (MachineInstr &Inst : Block) { + if (Inst.isCall() && Inst.getNumOperands() > 0) { + MachineOperand Callee = Inst.getOperand(0); + + if (Callee.isGlobal()) { + const GlobalValue *CalleeGlobal = Callee.getGlobal(); + + if (CalleeGlobal && CalleeGlobal->getName() == "_dummy_intrinsic") { + Erasures.push_back(&Inst); + } + } + } + } + } + for (MachineInstr *Target : Erasures) { + Target->eraseFromParent(); + // What to do about the callframe setup and teardown? + } + return !Erasures.empty(); +} + +} // end of anonymous namespace + +static RegisterTargetExtension Registrar(TargetPassConfig::MPEP_EarlyAsPossible, + &ExampleMachinePass::selfRegister); 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,25 @@ /// 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_PreEmitPass2, + 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; @@ -343,6 +364,26 @@ /// 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 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. + /// @p verifyAfter if true and adding a machine function pass add an extra + /// machine verification pass afterwards. + void addPass(Pass *P, bool VerifyAfter = true); + protected: // Helper to verify the analysis is really immutable. void setOpt(bool &Opt, bool Val); @@ -453,13 +494,6 @@ /// machine verification pass afterwards. AnalysisID addPass(AnalysisID PassID, bool verifyAfter = true); - /// 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. - /// @p verifyAfter if true and adding a machine function pass add an extra - /// machine verification pass afterwards. - void addPass(Pass *P, bool verifyAfter = true); - /// addMachinePasses helper to create the target-selected or overriden /// regalloc pass. virtual FunctionPass *createRegAllocPass(bool Optimized); @@ -468,11 +502,45 @@ /// 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 @@ -33,6 +33,7 @@ class Function; class GlobalValue; +class LLVMTargetMachine; class MachineFunctionPassManager; class MachineFunctionAnalysisManager; class MachineModuleInfoWrapperPass; @@ -155,6 +156,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. @@ -392,6 +402,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/Compiler.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/ManagedStatic.h" #include "llvm/Support/SaveAndRestore.h" #include "llvm/Support/Threading.h" #include "llvm/Target/CGPassBuilderOption.h" @@ -49,6 +50,7 @@ #include "llvm/Transforms/Utils/SymbolRewriter.h" #include #include +#include using namespace llvm; @@ -230,6 +232,12 @@ "disable-expand-reductions", cl::init(false), cl::Hidden, cl::desc("Disable the expand reduction intrinsics 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. @@ -671,6 +679,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 @@ -1079,11 +1116,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(); @@ -1098,6 +1140,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 @@ -1112,6 +1155,7 @@ addFastRegAlloc(); // Run post-ra passes. + applyAnyExtensions(MPEP_PostRegAlloc); addPostRegAlloc(); addPass(&FixupStatepointCallerSavedID); @@ -1136,6 +1180,7 @@ // Run pre-sched2 passes. addPreSched2(); + applyAnyExtensions(MPEP_PreSched2); if (EnableImplicitNullChecks) addPass(&ImplicitNullChecksID); @@ -1168,6 +1213,7 @@ addPass(&PatchableFunctionID); addPreEmitPass(); + applyAnyExtensions(MPEP_PreEmitPass); if (TM->Options.EnableIPRA) // Collect register usage information and produce a register mask of @@ -1204,11 +1250,14 @@ // Add passes that directly emit MI after all other MI passes. addPreEmitPass2(); + applyAnyExtensions(MPEP_PreEmitPass2); // Insert pseudo probe annotation for callsite profiling if (TM->Options.PseudoProbeForProfiling) addPass(createPseudoProbeInserter()); + 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 @@ -168,6 +168,7 @@ if (NOT WIN32) list(APPEND LLVM_TEST_DEPENDS Bye + MachineExtension ) endif() endif() diff --git a/llvm/test/Feature/machine_extension.ll b/llvm/test/Feature/machine_extension.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Feature/machine_extension.ll @@ -0,0 +1,23 @@ +; RUN: llc < %s 2>&1 | FileCheck %s --check-prefixes BOTH,NOLOAD +; RUN: llc %loadmachineextension < %s 2>&1 | FileCheck %s --check-prefixes BOTH,LOAD +; REQUIRES: examples +; UNSUPPORTED: windows + +; undefined extern functions which can't be elided normally +declare void @_dummy_intrinsic() +declare void @some_real_function() + +; Check that the _dummy_instrinc is passed through in the "NOLOAD" +; test case and replaced or removed by the machine-extension-example +; pass in the "LOAD" test case. Both cases should see the foo function +; label and a call to some_real_function. +; +; BOTH-LABEL: foo +; NOLOAD: _dummy_intrinsic +; LOAD-NOT: _dummy_intrinsic +; BOTH: some_real_function +define void @foo() { + call void @_dummy_intrinsic() + call void @some_real_function() + 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 @@ -212,6 +212,9 @@ if config.build_examples: config.available_features.add('examples') + config.substitutions.append(('%loadmachineextension', + '-load={}/MachineExtension{}'.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 @@ -28,6 +28,7 @@ ScalableVectorMVTsTest.cpp SelectionDAGAddressAnalysisTest.cpp TypeTraitsTest.cpp + TargetExtensionTest.cpp TargetOptionsTest.cpp TestAsmPrinter.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/Support/Host.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Target/TargetOptions.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{}, None)); + 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"; + } +}