[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
This commit is contained in:
Stuart Ellis 2021-08-12 11:42:08 +01:00 committed by Andrzej Warzynski
parent 8f359a80e4
commit f52fc591fa
18 changed files with 177 additions and 5 deletions

View File

@ -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]
//===----------------------------------------------------------------------===//

View File

@ -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)

View File

@ -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)

View File

@ -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
)

View File

@ -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");

View File

@ -30,6 +30,10 @@ struct MeasurementVisitor {
// Custom Consumer Actions
//===----------------------------------------------------------------------===//
class PluginParseTreeAction : public FrontendAction {
void ExecuteAction() override;
};
class InputOutputTestAction : public FrontendAction {
void ExecuteAction() override;
};

View File

@ -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.
///

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -479,3 +479,5 @@ void InitOnlyAction::ExecuteAction() {
"Use `-init-only` for testing purposes only");
ci.diagnostics().Report(DiagID);
}
void PluginParseTreeAction::ExecuteAction() {}

View File

@ -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)

View File

@ -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"

View File

@ -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.

View File

@ -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'

View File

@ -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__)

View File

@ -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

View File

@ -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)