[flang-commits] [flang] f52fc59 - [flang][driver] Add support for Frontend Plugins

Andrzej Warzynski via flang-commits flang-commits at lists.llvm.org
Thu Aug 12 03:56:16 PDT 2021


Author: Stuart Ellis
Date: 2021-08-12T11:42:16+01:00
New Revision: f52fc591fa34a8c85577108358b3b36c42b6d364

URL: https://github.com/llvm/llvm-project/commit/f52fc591fa34a8c85577108358b3b36c42b6d364
DIFF: https://github.com/llvm/llvm-project/commit/f52fc591fa34a8c85577108358b3b36c42b6d364.diff

LOG: [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

Added: 
    flang/examples/HelloWorld/CMakeLists.txt
    flang/examples/HelloWorld/HelloWorldPlugin.cpp
    flang/include/flang/Frontend/FrontendPluginRegistry.h
    flang/test/Driver/plugin-example.f90

Modified: 
    clang/include/clang/Driver/Options.td
    flang/CMakeLists.txt
    flang/examples/CMakeLists.txt
    flang/include/flang/Frontend/FrontendActions.h
    flang/include/flang/Frontend/FrontendOptions.h
    flang/lib/Frontend/CompilerInvocation.cpp
    flang/lib/Frontend/FrontendAction.cpp
    flang/lib/Frontend/FrontendActions.cpp
    flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp
    flang/test/CMakeLists.txt
    flang/test/Driver/driver-help.f90
    flang/test/lit.cfg.py
    flang/test/lit.site.cfg.py.in
    flang/tools/flang-driver/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 7525795bf9d3c..a91114f76ff07 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<f_Gr
 def discard_value_names : Flag<["-"], "discard-value-names">,
   HelpText<"Discard value names in LLVM IR">,
   MarshallingInfoFlag<CodeGenOpts<"DiscardValueNames">>;
-def load : Separate<["-"], "load">, MetaVarName<"<dsopath>">,
-  HelpText<"Load the named plugin (dynamic shared object)">;
-def plugin : Separate<["-"], "plugin">, MetaVarName<"<name>">,
-  HelpText<"Use the named plugin action instead of the default action (use \"help\" to list available options)">;
 def plugin_arg : JoinedAndSeparate<["-"], "plugin-arg-">,
     MetaVarName<"<name> <arg>">,
     HelpText<"Pass <arg> to plugin <name>">;
@@ -5836,6 +5832,12 @@ def init_only : Flag<["-"], "init-only">,
   HelpText<"Only execute frontend initialization">;
 
 } // let Group = Action_Group
+
+def load : Separate<["-"], "load">, MetaVarName<"<dsopath>">,
+  HelpText<"Load the named plugin (dynamic shared object)">;
+def plugin : Separate<["-"], "plugin">, MetaVarName<"<name>">,
+  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 fc85f44513163..cf6055075c383 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 3ca9feddf33e9..c4ef3bf20d4b0 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 0000000000000..8552284c80529
--- /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 0000000000000..11100384aed9f
--- /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<HelloWorldFlangPlugin> X(
+    "-hello-world", "Hello World Plugin example");

diff  --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h
index 72eb44223fe49..d30ae1dbed0ff 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 6dc397104efa1..26a5728bc3ea3 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<std::string> 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 0000000000000..555a06ff65df3
--- /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<PluginParseTreeAction>;
+
+} // namespace Fortran::frontend
+
+#endif // FLANG_FRONTEND_FRONTENDPLUGINREGISTRY_H

diff  --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp
index baa0f32c55c87..c16c9690f0599 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 <dsopath> 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 <name> 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 24efcc7cdc7f3..77700d2abec78 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 &currentInput) {
   this->currentInput_ = currentInput;
 }

diff  --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp
index d8bdb46c6958a..c12cafc02dbf3 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 a17b6b5079e91..677f8cd75c0ff 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<FrontendAction> CreateFrontendBaseAction(
     return std::make_unique<GetSymbolsSourcesAction>();
   case InitOnly:
     return std::make_unique<InitOnlyAction>();
+  case PluginAction: {
+    for (const FrontendPluginRegistry::entry &plugin :
+        FrontendPluginRegistry::entries()) {
+      if (plugin.getName() == ci.frontendOpts().ActionName) {
+        std::unique_ptr<PluginParseTreeAction> 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<FrontendAction> 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<FrontendAction> act(CreateFrontendAction(*flang));
   if (!act)

diff  --git a/flang/test/CMakeLists.txt b/flang/test/CMakeLists.txt
index e2e95596d30b8..8cd1e0ef93d14 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 288f03dc4dea7..bc2dc7e3fc991 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 <dir>               Add directory to the end of the list of include search paths
+! HELP-FC1-NEXT: -load <dsopath>        Load the named plugin (dynamic shared object)
 ! HELP-FC1-NEXT: -module-dir <dir>      Put MODULE files in <dir>
 ! HELP-FC1-NEXT: -module-suffix <suffix> Use <suffix> 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 <file>              Write output to <file>
 ! HELP-FC1-NEXT: -pedantic              Warn on language extensions
+! HELP-FC1-NEXT: -plugin <name>         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=<value>           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 0000000000000..eb009dea1f13f
--- /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 854b4e723884a..4c69b688d0426 100644
--- 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()
 
@@ -42,6 +44,14 @@
 # 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 746866bd722a6..5314be8502f6b 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 4c7ad220d223a..d747fb19dfc61 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)


        


More information about the flang-commits mailing list