From f52fc591fa34a8c85577108358b3b36c42b6d364 Mon Sep 17 00:00:00 2001 From: Stuart Ellis Date: Thu, 12 Aug 2021 11:42:08 +0100 Subject: [PATCH] [flang][driver] Add support for Frontend Plugins Introducing a plugin API and a simple HelloWorld Plugin example. This patch adds the `-load` and `-plugin` flags to frontend driver and the code around using custom frontend actions from within a plugin shared library object. It also adds to the Driver-help test to check the help option with the updated driver flags. Additionally, the patch creates a plugin-example test to check the HelloWorld plugin example runs correctly. As part of this, a new CMake flag (`FLANG_BUILD_EXAMPLES`) is added to allow the example to be built and for the test to run. This Plugin API has only been tested on Linux. Reviewed By: awarzynski Differential Revision: https://reviews.llvm.org/D106137 --- clang/include/clang/Driver/Options.td | 10 ++++--- flang/CMakeLists.txt | 2 ++ flang/examples/CMakeLists.txt | 6 +++++ flang/examples/HelloWorld/CMakeLists.txt | 7 +++++ flang/examples/HelloWorld/HelloWorldPlugin.cpp | 25 +++++++++++++++++ flang/include/flang/Frontend/FrontendActions.h | 4 +++ flang/include/flang/Frontend/FrontendOptions.h | 11 +++++++- .../flang/Frontend/FrontendPluginRegistry.h | 26 ++++++++++++++++++ flang/lib/Frontend/CompilerInvocation.cpp | 12 +++++++++ flang/lib/Frontend/FrontendAction.cpp | 3 +++ flang/lib/Frontend/FrontendActions.cpp | 2 ++ .../lib/FrontendTool/ExecuteCompilerInvocation.cpp | 31 ++++++++++++++++++++++ flang/test/CMakeLists.txt | 8 ++++++ flang/test/Driver/driver-help.f90 | 2 ++ flang/test/Driver/plugin-example.f90 | 11 ++++++++ flang/test/lit.cfg.py | 10 +++++++ flang/test/lit.site.cfg.py.in | 5 ++++ flang/tools/flang-driver/CMakeLists.txt | 7 +++++ 18 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 flang/examples/HelloWorld/CMakeLists.txt create mode 100644 flang/examples/HelloWorld/HelloWorldPlugin.cpp create mode 100644 flang/include/flang/Frontend/FrontendPluginRegistry.h create mode 100644 flang/test/Driver/plugin-example.f90 diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 7525795..a91114f 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5266,10 +5266,6 @@ def enable_noundef_analysis : Flag<["-"], "enable-noundef-analysis">, Group, HelpText<"Discard value names in LLVM IR">, MarshallingInfoFlag>; -def load : Separate<["-"], "load">, MetaVarName<"">, - HelpText<"Load the named plugin (dynamic shared object)">; -def plugin : Separate<["-"], "plugin">, MetaVarName<"">, - HelpText<"Use the named plugin action instead of the default action (use \"help\" to list available options)">; def plugin_arg : JoinedAndSeparate<["-"], "plugin-arg-">, MetaVarName<" ">, HelpText<"Pass to plugin ">; @@ -5836,6 +5832,12 @@ def init_only : Flag<["-"], "init-only">, HelpText<"Only execute frontend initialization">; } // let Group = Action_Group + +def load : Separate<["-"], "load">, MetaVarName<"">, + HelpText<"Load the named plugin (dynamic shared object)">; +def plugin : Separate<["-"], "plugin">, MetaVarName<"">, + HelpText<"Use the named plugin action instead of the default action (use \"help\" to list available options)">; + } // let Flags = [CC1Option, FC1Option, NoDriverOption] //===----------------------------------------------------------------------===// diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt index fc85f44..cf60550 100644 --- a/flang/CMakeLists.txt +++ b/flang/CMakeLists.txt @@ -390,6 +390,8 @@ if (FLANG_BUILD_TOOLS) add_subdirectory(tools) endif() add_subdirectory(runtime) + +option(FLANG_BUILD_EXAMPLES "Build Flang example programs by default." OFF) add_subdirectory(examples) if (FLANG_INCLUDE_TESTS) diff --git a/flang/examples/CMakeLists.txt b/flang/examples/CMakeLists.txt index 3ca9fed..c4ef3bf 100644 --- a/flang/examples/CMakeLists.txt +++ b/flang/examples/CMakeLists.txt @@ -1,3 +1,7 @@ +if(NOT FLANG_BUILD_EXAMPLES) + set(EXCLUDE_FROM_ALL ON) +endif() + # This test is not run by default as it requires input. add_executable(external-hello-world external-hello.cpp @@ -6,3 +10,5 @@ add_executable(external-hello-world target_link_libraries(external-hello-world FortranRuntime ) + +add_subdirectory(HelloWorld) diff --git a/flang/examples/HelloWorld/CMakeLists.txt b/flang/examples/HelloWorld/CMakeLists.txt new file mode 100644 index 0000000..8552284 --- /dev/null +++ b/flang/examples/HelloWorld/CMakeLists.txt @@ -0,0 +1,7 @@ +# TODO: Note that this is currently only available on Linux. +# On Windows, we would also have to specify e.g. `PLUGIN_TOOL`. +add_llvm_library( + flangHelloWorldPlugin + MODULE + HelloWorldPlugin.cpp +) diff --git a/flang/examples/HelloWorld/HelloWorldPlugin.cpp b/flang/examples/HelloWorld/HelloWorldPlugin.cpp new file mode 100644 index 0000000..1110038 --- /dev/null +++ b/flang/examples/HelloWorld/HelloWorldPlugin.cpp @@ -0,0 +1,25 @@ +//===-- HelloWorldPlugin.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 +// +//===----------------------------------------------------------------------===// +// +// Basic example Flang plugin which simply prints a Hello World statement +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/FrontendActions.h" +#include "flang/Frontend/FrontendPluginRegistry.h" + +using namespace Fortran::frontend; + +class HelloWorldFlangPlugin : public PluginParseTreeAction { + void ExecuteAction() override { + llvm::outs() << "Hello World from your new Flang plugin\n"; + } +}; + +static FrontendPluginRegistry::Add X( + "-hello-world", "Hello World Plugin example"); diff --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h index 72eb442..d30ae1d 100644 --- a/flang/include/flang/Frontend/FrontendActions.h +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -30,6 +30,10 @@ struct MeasurementVisitor { // Custom Consumer Actions //===----------------------------------------------------------------------===// +class PluginParseTreeAction : public FrontendAction { + void ExecuteAction() override; +}; + class InputOutputTestAction : public FrontendAction { void ExecuteAction() override; }; diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h index 6dc3971..26a5728 100644 --- a/flang/include/flang/Frontend/FrontendOptions.h +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -77,7 +77,10 @@ enum ActionKind { GetSymbolsSources, /// Only execute frontend initialization - InitOnly + InitOnly, + + /// Run a plugin action + PluginAction /// TODO: RunPreprocessor, EmitLLVM, EmitLLVMOnly, /// EmitCodeGenOnly, EmitAssembly, (...) @@ -248,6 +251,12 @@ struct FrontendOptions { // Source file encoding Fortran::parser::Encoding encoding{Fortran::parser::Encoding::UTF_8}; + /// The list of plugins to load. + std::vector plugins; + + /// The name of the action to run when using a plugin action. + std::string ActionName; + // Return the appropriate input kind for a file extension. For example, /// "*.f" would return Language::Fortran. /// diff --git a/flang/include/flang/Frontend/FrontendPluginRegistry.h b/flang/include/flang/Frontend/FrontendPluginRegistry.h new file mode 100644 index 0000000..555a06f --- /dev/null +++ b/flang/include/flang/Frontend/FrontendPluginRegistry.h @@ -0,0 +1,26 @@ +//===- FrontendPluginRegistry.h ---------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Pluggable Frontend Action Interface +// +//===----------------------------------------------------------------------===// + +#ifndef FLANG_FRONTEND_FRONTENDPLUGINREGISTRY_H +#define FLANG_FRONTEND_FRONTENDPLUGINREGISTRY_H + +#include "flang/Frontend/FrontendActions.h" +#include "llvm/Support/Registry.h" + +namespace Fortran::frontend { + +/// The frontend plugin registry. +using FrontendPluginRegistry = llvm::Registry; + +} // namespace Fortran::frontend + +#endif // FLANG_FRONTEND_FRONTENDPLUGINREGISTRY_H diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index baa0f32..c16c969 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -199,6 +199,18 @@ static bool ParseFrontendArgs(FrontendOptions &opts, llvm::opt::ArgList &args, } } + // Parsing -load option and storing shared object path + if (llvm::opt::Arg *a = args.getLastArg(clang::driver::options::OPT_load)) { + opts.plugins.push_back(a->getValue()); + } + + // Parsing -plugin option and storing plugin name and setting action + if (const llvm::opt::Arg *a = + args.getLastArg(clang::driver::options::OPT_plugin)) { + opts.programAction = PluginAction; + opts.ActionName = a->getValue(); + } + opts.outputFile = args.getLastArgValue(clang::driver::options::OPT_o); opts.showHelp = args.hasArg(clang::driver::options::OPT_help); opts.showVersion = args.hasArg(clang::driver::options::OPT_version); diff --git a/flang/lib/Frontend/FrontendAction.cpp b/flang/lib/Frontend/FrontendAction.cpp index 24efcc7..77700d2 100644 --- a/flang/lib/Frontend/FrontendAction.cpp +++ b/flang/lib/Frontend/FrontendAction.cpp @@ -10,6 +10,7 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/FrontendActions.h" #include "flang/Frontend/FrontendOptions.h" +#include "flang/Frontend/FrontendPluginRegistry.h" #include "flang/FrontendTool/Utils.h" #include "clang/Basic/DiagnosticFrontend.h" #include "llvm/Support/Errc.h" @@ -17,6 +18,8 @@ using namespace Fortran::frontend; +LLVM_INSTANTIATE_REGISTRY(FrontendPluginRegistry) + void FrontendAction::set_currentInput(const FrontendInputFile ¤tInput) { this->currentInput_ = currentInput; } diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp index d8bdb46..c12cafc 100644 --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -479,3 +479,5 @@ void InitOnlyAction::ExecuteAction() { "Use `-init-only` for testing purposes only"); ci.diagnostics().Report(DiagID); } + +void PluginParseTreeAction::ExecuteAction() {} diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index a17b6b5..677f8cd 100644 --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -13,6 +13,7 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/FrontendActions.h" +#include "flang/Frontend/FrontendPluginRegistry.h" #include "clang/Driver/Options.h" #include "llvm/Option/OptTable.h" #include "llvm/Option/Option.h" @@ -62,6 +63,19 @@ static std::unique_ptr CreateFrontendBaseAction( return std::make_unique(); case InitOnly: return std::make_unique(); + case PluginAction: { + for (const FrontendPluginRegistry::entry &plugin : + FrontendPluginRegistry::entries()) { + if (plugin.getName() == ci.frontendOpts().ActionName) { + std::unique_ptr p(plugin.instantiate()); + return std::move(p); + } + } + unsigned diagID = ci.diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "unable to find plugin '%0'"); + ci.diagnostics().Report(diagID) << ci.frontendOpts().ActionName; + return nullptr; + } default: break; // TODO: @@ -82,6 +96,7 @@ std::unique_ptr CreateFrontendAction(CompilerInstance &ci) { return act; } + bool ExecuteCompilerInvocation(CompilerInstance *flang) { // Honor -help. if (flang->frontendOpts().showHelp) { @@ -99,6 +114,22 @@ bool ExecuteCompilerInvocation(CompilerInstance *flang) { return true; } + // Load any requested plugins. + for (const std::string &Path : flang->frontendOpts().plugins) { + std::string Error; + if (llvm::sys::DynamicLibrary::LoadLibraryPermanently( + Path.c_str(), &Error)) { + unsigned diagID = flang->diagnostics().getCustomDiagID( + clang::DiagnosticsEngine::Error, "unable to load plugin '%0': '%1'"); + flang->diagnostics().Report(diagID) << Path << Error; + } + } + + // If there were errors in processing arguments, don't do anything else. + if (flang->diagnostics().hasErrorOccurred()) { + return false; + } + // Create and execute the frontend action. std::unique_ptr act(CreateFrontendAction(*flang)); if (!act) diff --git a/flang/test/CMakeLists.txt b/flang/test/CMakeLists.txt index e2e9559..8cd1e0e 100644 --- a/flang/test/CMakeLists.txt +++ b/flang/test/CMakeLists.txt @@ -2,7 +2,9 @@ # for use by Lit, and delegates to LLVM's lit test handlers. llvm_canonicalize_cmake_booleans( + FLANG_BUILD_EXAMPLES FLANG_STANDALONE_BUILD + LLVM_ENABLE_PLUGINS ) set(FLANG_TOOLS_DIR ${FLANG_BINARY_DIR}/bin) @@ -12,6 +14,8 @@ configure_lit_site_cfg( ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py MAIN_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py + PATHS + "SHLIBDIR" ) configure_lit_site_cfg( @@ -41,6 +45,10 @@ if (FLANG_INCLUDE_TESTS) endif() endif() +if (FLANG_BUILD_EXAMPLES) + list(APPEND FLANG_TEST_DEPENDS flangHelloWorldPlugin) +endif () + add_custom_target(flang-test-depends DEPENDS ${FLANG_TEST_DEPENDS}) add_lit_testsuite(check-flang "Running the Flang regression tests" diff --git a/flang/test/Driver/driver-help.f90 b/flang/test/Driver/driver-help.f90 index 288f03d..bc2dc7e 100644 --- a/flang/test/Driver/driver-help.f90 +++ b/flang/test/Driver/driver-help.f90 @@ -123,11 +123,13 @@ ! HELP-FC1-NEXT: -help Display available options ! HELP-FC1-NEXT: -init-only Only execute frontend initialization ! HELP-FC1-NEXT: -I Add directory to the end of the list of include search paths +! HELP-FC1-NEXT: -load Load the named plugin (dynamic shared object) ! HELP-FC1-NEXT: -module-dir Put MODULE files in ! HELP-FC1-NEXT: -module-suffix Use as the suffix for module files (the default value is `.mod`) ! HELP-FC1-NEXT: -nocpp Disable predefined and command line preprocessor macros ! HELP-FC1-NEXT: -o Write output to ! HELP-FC1-NEXT: -pedantic Warn on language extensions +! HELP-FC1-NEXT: -plugin Use the named plugin action instead of the default action (use "help" to list available options) ! HELP-FC1-NEXT: -P Disable linemarker output in -E mode ! HELP-FC1-NEXT: -std= Language standard to compile for ! HELP-FC1-NEXT: -test-io Run the InputOuputTest action. Use for development and testing only. diff --git a/flang/test/Driver/plugin-example.f90 b/flang/test/Driver/plugin-example.f90 new file mode 100644 index 0000000..eb009de --- /dev/null +++ b/flang/test/Driver/plugin-example.f90 @@ -0,0 +1,11 @@ +! Check that loading and running the Hello World plugin example results in the correct print statement +! Also check that when a plugin name isn't found, the error diagnostic is correct +! This requires that the examples are built (FLANG_BUILD_EXAMPLES=ON) + +! REQUIRES: new-flang-driver, plugins, examples, shell + +! RUN: %flang_fc1 -load %llvmshlibdir/flangHelloWorldPlugin%pluginext -plugin -hello-world %s 2>&1 | FileCheck %s +! CHECK: Hello World from your new Flang plugin + +! RUN: not %flang_fc1 -load %llvmshlibdir/flangHelloWorldPlugin%pluginext -plugin -wrong-name %s 2>&1 | FileCheck %s --check-prefix=ERROR +! ERROR: error: unable to find plugin '-wrong-name' diff --git a/flang/test/lit.cfg.py b/flang/test/lit.cfg.py index 854b4e7..4c69b68 100644 --- a/flang/test/lit.cfg.py +++ b/flang/test/lit.cfg.py @@ -30,6 +30,8 @@ config.suffixes = ['.c', '.cpp', '.f', '.F', '.ff', '.FOR', '.for', '.f77', '.f9 '.CUF', '.f18', '.F18', '.fir', '.f03', '.F03', '.f08', '.F08'] config.substitutions.append(('%PATH%', config.environment['PATH'])) +config.substitutions.append(('%llvmshlibdir', config.llvm_shlib_dir)) +config.substitutions.append(('%pluginext', config.llvm_plugin_ext)) llvm_config.use_default_substitutions() @@ -42,6 +44,14 @@ config.excludes = ['Inputs', 'CMakeLists.txt', 'README.txt', 'LICENSE.txt'] # config. config.available_features.add('new-flang-driver') +# If the flang examples are built, add examples to the config +if config.flang_examples: + config.available_features.add('examples') + +# Plugins (loadable modules) +if config.has_plugins: + config.available_features.add('plugins') + # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) diff --git a/flang/test/lit.site.cfg.py.in b/flang/test/lit.site.cfg.py.in index 746866b..5314be8 100644 --- a/flang/test/lit.site.cfg.py.in +++ b/flang/test/lit.site.cfg.py.in @@ -3,6 +3,8 @@ import sys config.llvm_tools_dir = "@LLVM_TOOLS_DIR@" +config.llvm_shlib_dir = path(r"@SHLIBDIR@") +config.llvm_plugin_ext = "@LLVM_PLUGIN_EXT@" config.lit_tools_dir = "@LLVM_LIT_TOOLS_DIR@" config.flang_obj_root = "@FLANG_BINARY_DIR@" config.flang_src_dir = "@FLANG_SOURCE_DIR@" @@ -10,8 +12,10 @@ config.flang_tools_dir = "@FLANG_TOOLS_DIR@" config.flang_intrinsic_modules_dir = "@FLANG_INTRINSIC_MODULES_DIR@" config.flang_llvm_tools_dir = "@CMAKE_BINARY_DIR@/bin" config.flang_lib_dir = "@CMAKE_BINARY_DIR@/lib" +config.flang_examples = @FLANG_BUILD_EXAMPLES@ config.python_executable = "@PYTHON_EXECUTABLE@" config.flang_standalone_build = @FLANG_STANDALONE_BUILD@ +config.has_plugins = @LLVM_ENABLE_PLUGINS@ config.cc = "@CMAKE_C_COMPILER@" # Support substitution of the tools_dir with user parameters. This is @@ -19,6 +23,7 @@ config.cc = "@CMAKE_C_COMPILER@" try: config.llvm_tools_dir = config.llvm_tools_dir % lit_config.params config.flang_tools_dir = config.flang_tools_dir % lit_config.params + config.llvm_shlib_dir = config.llvm_shlib_dir % lit_config.params except KeyError: e = sys.exc_info()[1] key, = e.args diff --git a/flang/tools/flang-driver/CMakeLists.txt b/flang/tools/flang-driver/CMakeLists.txt index 4c7ad22..d747fb1 100644 --- a/flang/tools/flang-driver/CMakeLists.txt +++ b/flang/tools/flang-driver/CMakeLists.txt @@ -27,4 +27,11 @@ clang_target_link_libraries(flang-new clangBasic ) +option(FLANG_PLUGIN_SUPPORT "Build Flang with plugin support." ON) + +# Enable support for plugins, which need access to symbols from flang-new +if(FLANG_PLUGIN_SUPPORT) + export_executable_symbols_for_plugins(flang-new) +endif() + install(TARGETS flang-new DESTINATION bin) -- 2.7.4