From e8f3ae9da07c47d5ee4d351a8085385e3df9390d Mon Sep 17 00:00:00 2001 From: Philip Pfaffe Date: Thu, 5 Apr 2018 11:29:37 +0000 Subject: [PATCH] [Plugins] Add a slim plugin API to work together with the new PM Summary: Add a new plugin API. This closes the gap between pass registration and out-of-tree passes for the new PassManager. Unlike with the existing API, interaction with a plugin is always initiated from the tools perspective. I.e., when a plugin is loaded, it resolves and calls a well-known symbol `llvmGetPassPluginInfo` to obtain details about the plugin. The fundamental motivation is to get rid of as many global constructors as possible. The API exposed by the plugin info is kept intentionally minimal. Reviewers: chandlerc Reviewed By: chandlerc Subscribers: bollu, grosser, lksbhm, mgorny, llvm-commits Differential Revision: https://reviews.llvm.org/D35258 llvm-svn: 329273 --- llvm/include/llvm/Demangle/Compiler.h | 18 ++++ llvm/include/llvm/Passes/PassPlugin.h | 114 ++++++++++++++++++++++++++ llvm/include/llvm/Support/Compiler.h | 1 + llvm/lib/Passes/CMakeLists.txt | 1 + llvm/lib/Passes/PassPlugin.cpp | 50 +++++++++++ llvm/tools/opt/NewPMDriver.cpp | 17 ++++ llvm/unittests/CMakeLists.txt | 1 + llvm/unittests/Passes/CMakeLists.txt | 21 +++++ llvm/unittests/Passes/PluginsTest.cpp | 53 ++++++++++++ llvm/unittests/Passes/TestPlugin.cxx | 39 +++++++++ llvm/unittests/Passes/TestPlugin.h | 2 + 11 files changed, 317 insertions(+) create mode 100644 llvm/include/llvm/Passes/PassPlugin.h create mode 100644 llvm/lib/Passes/PassPlugin.cpp create mode 100644 llvm/unittests/Passes/CMakeLists.txt create mode 100644 llvm/unittests/Passes/PluginsTest.cpp create mode 100644 llvm/unittests/Passes/TestPlugin.cxx create mode 100644 llvm/unittests/Passes/TestPlugin.h diff --git a/llvm/include/llvm/Demangle/Compiler.h b/llvm/include/llvm/Demangle/Compiler.h index c996f9b71e5b..da1e2715f2aa 100644 --- a/llvm/include/llvm/Demangle/Compiler.h +++ b/llvm/include/llvm/Demangle/Compiler.h @@ -503,4 +503,22 @@ void AnnotateIgnoreWritesEnd(const char *file, int line); #define LLVM_ENABLE_EXCEPTIONS 1 #endif +/// \macro LLVM_PLUGIN_IMPORT +/// \brief Used to import the well-known entry point for registering loaded pass +/// plugins +#ifdef WIN32 +#define LLVM_PLUGIN_IMPORT __declspec(dllimport) +#else +#define LLVM_PLUGIN_IMPORT +#endif + +/// \macro LLVM_PLUGIN_EXPORT +/// \brief Used to export the well-known entry point for registering loaded pass +/// plugins +#ifdef WIN32 +#define LLVM_PLUGIN_EXPORT __declspec(dllexport) +#else +#define LLVM_PLUGIN_EXPORT +#endif + #endif diff --git a/llvm/include/llvm/Passes/PassPlugin.h b/llvm/include/llvm/Passes/PassPlugin.h new file mode 100644 index 000000000000..c61a19746309 --- /dev/null +++ b/llvm/include/llvm/Passes/PassPlugin.h @@ -0,0 +1,114 @@ +//===- llvm/Passes/PassPlugin.h - Public Plugin API -----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines the public entry point for new-PM pass plugins. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_PASSES_PASSPLUGIN_H +#define LLVM_PASSES_PASSPLUGIN_H + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/DynamicLibrary.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace llvm { +class PassBuilder; + +/// \macro LLVM_PLUGIN_API_VERSION +/// Identifies the API version understood by this plugin. +/// +/// When a plugin is loaded, the driver will check it's supported plugin version +/// against that of the plugin. A mismatch is an error. The supported version +/// will be incremented for ABI-breaking changes to the \c PassPluginLibraryInfo +/// struct, i.e. when callbacks are added, removed, or reordered. +#define LLVM_PLUGIN_API_VERSION 1 + +extern "C" { +/// Information about the plugin required to load its passes +/// +/// This struct defines the core interface for pass plugins and is supposed to +/// be filled out by plugin implementors. LLVM-side users of a plugin are +/// expected to use the \c PassPlugin class below to interface with it. +struct PassPluginLibraryInfo { + /// The API version understood by this plugin, usually \c + /// LLVM_PLUGIN_API_VERSION + uint32_t APIVersion; + /// A meaningful name of the plugin. + const char *PluginName; + /// The version of the plugin. + const char *PluginVersion; + + /// The callback for registering plugin passes with a \c PassBuilder + /// instance + void (*RegisterPassBuilderCallbacks)(PassBuilder &); +}; +} + +/// A loaded pass plugin. +/// +/// An instance of this class wraps a loaded pass plugin and gives access to +/// its interface defined by the \c PassPluginLibraryInfo it exposes. +class PassPlugin { +public: + /// Attempts to load a pass plugin from a given file. + /// + /// \returns Returns an error if either the library cannot be found or loaded, + /// there is no public entry point, or the plugin implements the wrong API + /// version. + static Expected Load(const std::string &Filename); + + /// Get the filename of the loaded plugin. + StringRef getFilename() const { return Filename; } + + /// Get the plugin name + StringRef getPluginName() const { return Info.PluginName; } + + /// Get the plugin version + StringRef getPluginVersion() const { return Info.PluginVersion; } + + /// Get the plugin API version + uint32_t getAPIVersion() const { return Info.APIVersion; } + + /// Invoke the PassBuilder callback registration + void registerPassBuilderCallbacks(PassBuilder &PB) const { + Info.RegisterPassBuilderCallbacks(PB); + } + +private: + PassPlugin(const std::string &Filename, const sys::DynamicLibrary &Library) + : Filename(Filename), Library(Library), Info() {} + + std::string Filename; + sys::DynamicLibrary Library; + PassPluginLibraryInfo Info; +}; +} + +/// The public entry point for a pass plugin. +/// +/// When a plugin is loaded by the driver, it will call this entry point to +/// obtain information about this plugin and about how to register its passes. +/// This function needs to be implemented by the plugin, see the example below: +/// +/// ``` +/// extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK +/// LLVM_PLUGIN_EXPORT llvmGetPassPluginInfo() { +/// return { +/// LLVM_PLUGIN_API_VERSION, "MyPlugin", "v0.1", [](PassBuilder &PB) { ... } +/// }; +/// } +/// ``` +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK LLVM_PLUGIN_IMPORT +llvmGetPassPluginInfo(); + +#endif /* LLVM_PASSES_PASSPLUGIN_H */ diff --git a/llvm/include/llvm/Support/Compiler.h b/llvm/include/llvm/Support/Compiler.h index 43a96e49ce93..c42a70b310db 100644 --- a/llvm/include/llvm/Support/Compiler.h +++ b/llvm/include/llvm/Support/Compiler.h @@ -16,4 +16,5 @@ // //===----------------------------------------------------------------------===// + #include "llvm/Demangle/Compiler.h" diff --git a/llvm/lib/Passes/CMakeLists.txt b/llvm/lib/Passes/CMakeLists.txt index 8fcadcf2c958..cc39dfb798e4 100644 --- a/llvm/lib/Passes/CMakeLists.txt +++ b/llvm/lib/Passes/CMakeLists.txt @@ -1,5 +1,6 @@ add_llvm_library(LLVMPasses PassBuilder.cpp + PassPlugin.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Passes diff --git a/llvm/lib/Passes/PassPlugin.cpp b/llvm/lib/Passes/PassPlugin.cpp new file mode 100644 index 000000000000..8f689ee505c3 --- /dev/null +++ b/llvm/lib/Passes/PassPlugin.cpp @@ -0,0 +1,50 @@ +//===- lib/Passes/PassPluginLoader.cpp - Load Plugins for New PM Passes ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +Expected PassPlugin::Load(const std::string &Filename) { + std::string Error; + auto Library = + sys::DynamicLibrary::getPermanentLibrary(Filename.c_str(), &Error); + if (!Library.isValid()) + return make_error(Twine("Could not load library '") + + Filename + "': " + Error, + inconvertibleErrorCode()); + + PassPlugin P{Filename, Library}; + auto *getDetailsFn = + Library.SearchForAddressOfSymbol("llvmGetPassPluginInfo"); + + if (!getDetailsFn) + // If the symbol isn't found, this is probably a legacy plugin, which is an + // error + return make_error(Twine("Plugin entry point not found in '") + + Filename + "'. Is this a legacy plugin?", + inconvertibleErrorCode()); + + P.Info = reinterpret_cast(getDetailsFn)(); + + if (P.Info.APIVersion != LLVM_PLUGIN_API_VERSION) + return make_error( + Twine("Wrong API version on plugin '") + Filename + "'. Got version " + + Twine(P.Info.APIVersion) + ", supported version is " + + Twine(LLVM_PLUGIN_API_VERSION) + ".", + inconvertibleErrorCode()); + + if (!P.Info.RegisterPassBuilderCallbacks) + return make_error(Twine("Empty entry callback in plugin '") + + Filename + "'.'", + inconvertibleErrorCode()); + + return P; +} diff --git a/llvm/tools/opt/NewPMDriver.cpp b/llvm/tools/opt/NewPMDriver.cpp index d596103bebc6..f42241ccf95e 100644 --- a/llvm/tools/opt/NewPMDriver.cpp +++ b/llvm/tools/opt/NewPMDriver.cpp @@ -27,6 +27,7 @@ #include "llvm/IR/PassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ToolOutputFile.h" @@ -41,6 +42,10 @@ static cl::opt DebugPM("debug-pass-manager", cl::Hidden, cl::desc("Print pass management debugging information")); +static cl::list + PassPlugins("load-pass-plugin", + cl::desc("Load passes from plugin library")); + // This flag specifies a textual description of the alias analysis pipeline to // use when querying for aliasing information. It only works in concert with // the "passes" flag above. @@ -210,6 +215,18 @@ bool llvm::runPassPipeline(StringRef Arg0, Module &M, TargetMachine *TM, PassBuilder PB(TM, P); registerEPCallbacks(PB, VerifyEachPass, DebugPM); + // Load requested pass plugins and let them register pass builder callbacks + for (auto &PluginFN : PassPlugins) { + auto PassPlugin = PassPlugin::Load(PluginFN); + if (!PassPlugin) { + errs() << "Failed to load passes from '" << PluginFN + << "'. Request ignored.\n"; + continue; + } + + PassPlugin->registerPassBuilderCallbacks(PB); + } + // Register a callback that creates the debugify passes as needed. PB.registerPipelineParsingCallback( [](StringRef Name, ModulePassManager &MPM, diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt index 94aca0566256..f266b2fb3ad4 100644 --- a/llvm/unittests/CMakeLists.txt +++ b/llvm/unittests/CMakeLists.txt @@ -22,6 +22,7 @@ add_subdirectory(Object) add_subdirectory(BinaryFormat) add_subdirectory(ObjectYAML) add_subdirectory(Option) +add_subdirectory(Passes) add_subdirectory(ProfileData) add_subdirectory(Support) add_subdirectory(Target) diff --git a/llvm/unittests/Passes/CMakeLists.txt b/llvm/unittests/Passes/CMakeLists.txt new file mode 100644 index 000000000000..37daed356dad --- /dev/null +++ b/llvm/unittests/Passes/CMakeLists.txt @@ -0,0 +1,21 @@ +set(LLVM_LINK_COMPONENTS Support Passes Core) + +add_llvm_unittest(PluginsTests PluginsTest.cpp) +export_executable_symbols(PluginsTests) + +add_library(TestPlugin SHARED TestPlugin.cxx) + +set_output_directory(TestPlugin + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} + ) + +set_target_properties(TestPlugin + PROPERTIES PREFIX "" + SUFFIX ".so" + ) + +llvm_map_components_to_libnames(LLVMDependencies ${LLVM_LINK_COMPONENTS}) +target_link_libraries(TestPlugin ${LLVMDependencies}) + +add_dependencies(PluginsTests TestPlugin) diff --git a/llvm/unittests/Passes/PluginsTest.cpp b/llvm/unittests/Passes/PluginsTest.cpp new file mode 100644 index 000000000000..df1ba1c5b2b4 --- /dev/null +++ b/llvm/unittests/Passes/PluginsTest.cpp @@ -0,0 +1,53 @@ +//===- unittests/Passes/Plugins/PluginsTest.cpp ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/CGSCCPassManager.h" +#include "llvm/IR/PassManager.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" +#include "llvm/Transforms/Scalar/LoopPassManager.h" +#include "gtest/gtest.h" + +#include "TestPlugin.h" + +using namespace llvm; + +void anchor() {} + +static std::string LibPath(const std::string Name = "TestPlugin") { + const std::vector &Argvs = + testing::internal::GetArgvs(); + const char *Argv0 = Argvs.size() > 0 ? Argvs[0].c_str() : "PluginsTests"; + void *Ptr = (void *)anchor; + std::string Path = sys::fs::getMainExecutable(Argv0, Ptr); + llvm::SmallString<256> Buf{sys::path::parent_path(Path)}; + sys::path::append(Buf, (Name + ".so").c_str()); + return Buf.str(); +} + +TEST(PluginsTests, LoadPlugin) { + auto PluginPath = LibPath(); + ASSERT_NE("", PluginPath); + + Expected Plugin = PassPlugin::Load(PluginPath); + ASSERT_TRUE(!!Plugin) << "Plugin path: " << PluginPath; + + ASSERT_EQ(TEST_PLUGIN_NAME, Plugin->getPluginName()); + ASSERT_EQ(TEST_PLUGIN_VERSION, Plugin->getPluginVersion()); + + PassBuilder PB; + ModulePassManager PM; + ASSERT_FALSE(PB.parsePassPipeline(PM, "plugin-pass")); + + Plugin->registerPassBuilderCallbacks(PB); + ASSERT_TRUE(PB.parsePassPipeline(PM, "plugin-pass")); +} diff --git a/llvm/unittests/Passes/TestPlugin.cxx b/llvm/unittests/Passes/TestPlugin.cxx new file mode 100644 index 000000000000..adb666e69d73 --- /dev/null +++ b/llvm/unittests/Passes/TestPlugin.cxx @@ -0,0 +1,39 @@ +//===- unittests/Passes/Plugins/Plugin.cxx --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" + +#include "TestPlugin.h" + +using namespace llvm; + +struct TestModulePass : public PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) { + return PreservedAnalyses::all(); + } +}; + +void registerCallbacks(PassBuilder &PB) { + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &PM, + ArrayRef InnerPipeline) { + if (Name == "plugin-pass") { + PM.addPass(TestModulePass()); + return true; + } + return false; + }); +} + +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK LLVM_PLUGIN_EXPORT +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, TEST_PLUGIN_NAME, TEST_PLUGIN_VERSION, + registerCallbacks}; +} diff --git a/llvm/unittests/Passes/TestPlugin.h b/llvm/unittests/Passes/TestPlugin.h new file mode 100644 index 000000000000..801a89065cda --- /dev/null +++ b/llvm/unittests/Passes/TestPlugin.h @@ -0,0 +1,2 @@ +#define TEST_PLUGIN_NAME "TestPlugin" +#define TEST_PLUGIN_VERSION "0.1-unit"