diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -5221,10 +5221,6 @@ def discard_value_names : Flag<["-"], "discard-value-names">, 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 ">; @@ -5791,6 +5787,12 @@ 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 --- a/flang/CMakeLists.txt +++ b/flang/CMakeLists.txt @@ -401,6 +401,8 @@ 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 --- a/flang/examples/CMakeLists.txt +++ b/flang/examples/CMakeLists.txt @@ -6,3 +6,5 @@ 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 --- /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 --- /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 --- a/flang/include/flang/Frontend/FrontendActions.h +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -30,6 +30,10 @@ // 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 --- a/flang/include/flang/Frontend/FrontendOptions.h +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -77,7 +77,10 @@ GetSymbolsSources, /// Only execute frontend initialization - InitOnly + InitOnly, + + /// Run a plugin action + PluginAction /// TODO: RunPreprocessor, EmitLLVM, EmitLLVMOnly, /// EmitCodeGenOnly, EmitAssembly, (...) @@ -249,6 +252,12 @@ // 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; + public: FrontendOptions() : showHelp_(false), showVersion_(false), instrumentedParse_(false), diff --git a/flang/include/flang/Frontend/FrontendPluginRegistry.h b/flang/include/flang/Frontend/FrontendPluginRegistry.h new file mode 100644 --- /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 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -199,6 +199,18 @@ } } + // 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 --- 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 --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -492,3 +492,5 @@ "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 --- 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" @@ -79,6 +80,19 @@ case InitOnly: return std::make_unique(); break; + 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: @@ -100,6 +114,7 @@ return act; } + bool ExecuteCompilerInvocation(CompilerInstance *flang) { // Honor -help. if (flang->frontendOpts().showHelp_) { @@ -117,6 +132,21 @@ 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 --- 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 @@ ${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py MAIN_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py + PATHS + "SHLIBDIR" ) configure_lit_site_cfg( @@ -45,6 +49,10 @@ 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 --- a/flang/test/Driver/driver-help.f90 +++ b/flang/test/Driver/driver-help.f90 @@ -109,11 +109,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: -std= Language standard to compile for ! HELP-FC1-NEXT: -test-io Run the InputOuputTest action. Use for development and testing only. ! HELP-FC1-NEXT: -U Undefine macro diff --git a/flang/test/Driver/plugin-example.f90 b/flang/test/Driver/plugin-example.f90 new file mode 100644 --- /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 --- a/flang/test/lit.cfg.py +++ b/flang/test/lit.cfg.py @@ -30,6 +30,8 @@ '.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() @@ -45,6 +47,14 @@ else: config.available_features.add('old-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 --- 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_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@" # Control the regression test for flang-new driver @@ -24,6 +28,7 @@ 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 --- a/flang/tools/flang-driver/CMakeLists.txt +++ b/flang/tools/flang-driver/CMakeLists.txt @@ -27,4 +27,11 @@ 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)