diff --git a/clang/include/clang/Driver/Driver.h b/clang/include/clang/Driver/Driver.h --- a/clang/include/clang/Driver/Driver.h +++ b/clang/include/clang/Driver/Driver.h @@ -301,7 +301,7 @@ StringRef CustomResourceDir = ""); Driver(StringRef ClangExecutable, StringRef TargetTriple, - DiagnosticsEngine &Diags, + DiagnosticsEngine &Diags, std::string Title = "clang LLVM compiler", IntrusiveRefCntPtr VFS = nullptr); /// @name Accessors diff --git a/clang/include/clang/Driver/Options.h b/clang/include/clang/Driver/Options.h --- a/clang/include/clang/Driver/Options.h +++ b/clang/include/clang/Driver/Options.h @@ -34,7 +34,9 @@ CC1AsOption = (1 << 11), NoDriverOption = (1 << 12), LinkOption = (1 << 13), - Ignored = (1 << 14), + FlangOption = (1 << 14), + FC1Option = (1 << 15), + Ignored = (1 << 16), }; enum ID { 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 @@ -56,6 +56,13 @@ // be used), add this flag. def LinkOption : OptionFlag; +// FlangOption - This is considered a "core" Flang option, available in +// flang mode. +def FlangOption : OptionFlag; + +// FC1Option - This option should be accepted by flang -fc1. +def FC1Option : OptionFlag; + // A short name to show in documentation. The name will be interpreted as rST. class DocName { string DocName = name; } @@ -2100,7 +2107,7 @@ Flags<[DriverOption]>, HelpText<"Restore the default behavior of not embedding source text in DWARF debug sections">; def headerpad__max__install__names : Joined<["-"], "headerpad_max_install_names">; -def help : Flag<["-", "--"], "help">, Flags<[CC1Option,CC1AsOption]>, +def help : Flag<["-", "--"], "help">, Flags<[CC1Option,CC1AsOption, FC1Option, FlangOption]>, HelpText<"Display available options">; def ibuiltininc : Flag<["-"], "ibuiltininc">, HelpText<"Enable builtin #include directories even when -nostdinc is used " @@ -3049,7 +3056,8 @@ def _serialize_diags : Separate<["-", "--"], "serialize-diagnostics">, Flags<[DriverOption]>, HelpText<"Serialize compiler diagnostics to a file">; // We give --version different semantics from -version. -def _version : Flag<["--"], "version">, Flags<[CoreOption, CC1Option]>, +def _version : Flag<["--"], "version">, + Flags<[CoreOption, CC1Option, FC1Option, FlangOption]>, HelpText<"Print version information">; def _signed_char : Flag<["--"], "signed-char">, Alias; def _std : Separate<["--"], "std">, Alias; diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -128,12 +128,12 @@ } Driver::Driver(StringRef ClangExecutable, StringRef TargetTriple, - DiagnosticsEngine &Diags, + DiagnosticsEngine &Diags, std::string Title, IntrusiveRefCntPtr VFS) : Diags(Diags), VFS(std::move(VFS)), Mode(GCCMode), SaveTemps(SaveTempsNone), BitcodeEmbed(EmbedNone), LTOMode(LTOK_None), ClangExecutable(ClangExecutable), SysRoot(DEFAULT_SYSROOT), - DriverTitle("clang LLVM compiler"), CCPrintOptionsFilename(nullptr), + DriverTitle(Title), CCPrintOptionsFilename(nullptr), CCPrintHeadersFilename(nullptr), CCLogDiagnosticsFilename(nullptr), CCCPrintBindings(false), CCPrintOptions(false), CCPrintHeaders(false), CCLogDiagnostics(false), CCGenDiagnostics(false), @@ -1571,6 +1571,9 @@ if (!ShowHidden) ExcludedFlagsBitmask |= HelpHidden; + if (IsFlangMode()) + IncludedFlagsBitmask |= options::FlangOption; + std::string Usage = llvm::formatv("{0} [options] file...", Name).str(); getOpts().PrintHelp(llvm::outs(), Usage.c_str(), DriverTitle.c_str(), IncludedFlagsBitmask, ExcludedFlagsBitmask, @@ -1578,9 +1581,13 @@ } void Driver::PrintVersion(const Compilation &C, raw_ostream &OS) const { - // FIXME: The following handlers should use a callback mechanism, we don't - // know what the client would like to do. - OS << getClangFullVersion() << '\n'; + if (IsFlangMode()) { + OS << getClangToolFullVersion("flang-new") << '\n'; + } else { + // FIXME: The following handlers should use a callback mechanism, we don't + // know what the client would like to do. + OS << getClangFullVersion() << '\n'; + } const ToolChain &TC = C.getDefaultToolChain(); OS << "Target: " << TC.getTripleString() << '\n'; @@ -1618,7 +1625,7 @@ std::vector SuggestedCompletions; std::vector Flags; - unsigned short DisableFlags = + unsigned int DisableFlags = options::NoDriverOption | options::Unsupported | options::Ignored; // Distinguish "--autocomplete=-someflag" and "--autocomplete=-someflag," diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp --- a/clang/lib/Driver/ToolChains/Flang.cpp +++ b/clang/lib/Driver/ToolChains/Flang.cpp @@ -69,11 +69,13 @@ CmdArgs.push_back(Input.getFilename()); const auto& D = C.getDriver(); - const char* Exec = Args.MakeArgString(D.GetProgramPath("flang", TC)); + // TODO: Replace flang-new with flang once the new driver replaces the + // throwaway driver + const char *Exec = Args.MakeArgString(D.GetProgramPath("flang-new", TC)); C.addCommand(std::make_unique( JA, *this, ResponseFileSupport::AtFileUTF8(), Exec, CmdArgs, Inputs)); } -Flang::Flang(const ToolChain &TC) : Tool("flang", "flang frontend", TC) {} +Flang::Flang(const ToolChain &TC) : Tool("flang-new", "flang frontend", TC) {} Flang::~Flang() {} diff --git a/clang/lib/Frontend/CreateInvocationFromCommandLine.cpp b/clang/lib/Frontend/CreateInvocationFromCommandLine.cpp --- a/clang/lib/Frontend/CreateInvocationFromCommandLine.cpp +++ b/clang/lib/Frontend/CreateInvocationFromCommandLine.cpp @@ -40,8 +40,8 @@ Args.push_back("-fsyntax-only"); // FIXME: We shouldn't have to pass in the path info. - driver::Driver TheDriver(Args[0], llvm::sys::getDefaultTargetTriple(), - *Diags, VFS); + driver::Driver TheDriver(Args[0], llvm::sys::getDefaultTargetTriple(), *Diags, + "clang LLVM compiler", VFS); // Don't check that inputs exist, they may have been remapped. TheDriver.setCheckInputsExist(false); diff --git a/clang/lib/Tooling/Tooling.cpp b/clang/lib/Tooling/Tooling.cpp --- a/clang/lib/Tooling/Tooling.cpp +++ b/clang/lib/Tooling/Tooling.cpp @@ -78,7 +78,7 @@ IntrusiveRefCntPtr VFS) { driver::Driver *CompilerDriver = new driver::Driver(BinaryName, llvm::sys::getDefaultTargetTriple(), - *Diagnostics, std::move(VFS)); + *Diagnostics, "clang LLVM compiler", std::move(VFS)); CompilerDriver->setTitle("clang_based_tool"); return CompilerDriver; } diff --git a/clang/test/Driver/flang/flang.f90 b/clang/test/Driver/flang/flang.f90 --- a/clang/test/Driver/flang/flang.f90 +++ b/clang/test/Driver/flang/flang.f90 @@ -13,7 +13,7 @@ ! * (no type specified, resulting in an object file) ! All invocations should begin with flang -fc1, consume up to here. -! ALL-LABEL: "{{[^"]*}}flang" "-fc1" +! ALL-LABEL: "{{[^"]*}}flang-new" "-fc1" ! Check that f90 files are not treated as "previously preprocessed" ! ... in --driver-mode=flang. diff --git a/clang/test/Driver/flang/flang_ucase.F90 b/clang/test/Driver/flang/flang_ucase.F90 --- a/clang/test/Driver/flang/flang_ucase.F90 +++ b/clang/test/Driver/flang/flang_ucase.F90 @@ -13,7 +13,7 @@ ! * (no type specified, resulting in an object file) ! All invocations should begin with flang -fc1, consume up to here. -! ALL-LABEL: "{{[^"]*}}flang" "-fc1" +! ALL-LABEL: "{{[^"]*}}flang-new" "-fc1" ! Check that f90 files are not treated as "previously preprocessed" ! ... in --driver-mode=flang. diff --git a/clang/test/Driver/flang/multiple-inputs-mixed.f90 b/clang/test/Driver/flang/multiple-inputs-mixed.f90 --- a/clang/test/Driver/flang/multiple-inputs-mixed.f90 +++ b/clang/test/Driver/flang/multiple-inputs-mixed.f90 @@ -1,7 +1,7 @@ ! Check that flang can handle mixed C and fortran inputs. ! RUN: %clang --driver-mode=flang -### -fsyntax-only %S/Inputs/one.f90 %S/Inputs/other.c 2>&1 | FileCheck --check-prefixes=CHECK-SYNTAX-ONLY %s -! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang{{[^"/]*}}" "-fc1" +! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang-new{{[^"/]*}}" "-fc1" ! CHECK-SYNTAX-ONLY: "{{[^"]*}}/Inputs/one.f90" ! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}clang{{[^"/]*}}" "-cc1" ! CHECK-SYNTAX-ONLY: "{{[^"]*}}/Inputs/other.c" diff --git a/clang/test/Driver/flang/multiple-inputs.f90 b/clang/test/Driver/flang/multiple-inputs.f90 --- a/clang/test/Driver/flang/multiple-inputs.f90 +++ b/clang/test/Driver/flang/multiple-inputs.f90 @@ -1,7 +1,7 @@ ! Check that flang driver can handle multiple inputs at once. ! RUN: %clang --driver-mode=flang -### -fsyntax-only %S/Inputs/one.f90 %S/Inputs/two.f90 2>&1 | FileCheck --check-prefixes=CHECK-SYNTAX-ONLY %s -! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang" "-fc1" +! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang-new" "-fc1" ! CHECK-SYNTAX-ONLY: "{{[^"]*}}/Inputs/one.f90" -! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang" "-fc1" +! CHECK-SYNTAX-ONLY-LABEL: "{{[^"]*}}flang-new" "-fc1" ! CHECK-SYNTAX-ONLY: "{{[^"]*}}/Inputs/two.f90" diff --git a/clang/unittests/Driver/SanitizerArgsTest.cpp b/clang/unittests/Driver/SanitizerArgsTest.cpp --- a/clang/unittests/Driver/SanitizerArgsTest.cpp +++ b/clang/unittests/Driver/SanitizerArgsTest.cpp @@ -57,7 +57,7 @@ new DiagnosticIDs, Opts, new TextDiagnosticPrinter(llvm::errs(), Opts.get())); DriverInstance.emplace(ClangBinary, "x86_64-unknown-linux-gnu", Diags, - prepareFS(ExtraFiles)); + "clang LLVM compiler", prepareFS(ExtraFiles)); std::vector Args = {ClangBinary}; for (const auto &A : ExtraArgs) diff --git a/clang/unittests/Driver/ToolChainTest.cpp b/clang/unittests/Driver/ToolChainTest.cpp --- a/clang/unittests/Driver/ToolChainTest.cpp +++ b/clang/unittests/Driver/ToolChainTest.cpp @@ -35,7 +35,7 @@ IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); Driver TheDriver("/bin/clang", "arm-linux-gnueabihf", Diags, - InMemoryFileSystem); + "clang LLVM compiler", InMemoryFileSystem); const char *EmptyFiles[] = { "foo.cpp", @@ -89,7 +89,7 @@ IntrusiveRefCntPtr InMemoryFileSystem( new llvm::vfs::InMemoryFileSystem); Driver TheDriver("/home/test/bin/clang", "arm-linux-gnueabi", Diags, - InMemoryFileSystem); + "clang LLVM compiler", InMemoryFileSystem); const char *EmptyFiles[] = { "foo.cpp", "/home/test/lib/gcc/arm-linux-gnueabi/4.6.1/crtbegin.o", @@ -130,13 +130,13 @@ new llvm::vfs::InMemoryFileSystem); Driver CCDriver("/home/test/bin/clang", "arm-linux-gnueabi", Diags, - InMemoryFileSystem); + "clang LLVM compiler", InMemoryFileSystem); CCDriver.setCheckInputsExist(false); Driver CXXDriver("/home/test/bin/clang++", "arm-linux-gnueabi", Diags, - InMemoryFileSystem); + "clang LLVM compiler", InMemoryFileSystem); CXXDriver.setCheckInputsExist(false); Driver CLDriver("/home/test/bin/clang-cl", "arm-linux-gnueabi", Diags, - InMemoryFileSystem); + "clang LLVM compiler", InMemoryFileSystem); CLDriver.setCheckInputsExist(false); std::unique_ptr CC(CCDriver.BuildCompilation( diff --git a/flang/CMakeLists.txt b/flang/CMakeLists.txt --- a/flang/CMakeLists.txt +++ b/flang/CMakeLists.txt @@ -17,6 +17,7 @@ endif() option(LINK_WITH_FIR "Link driver with FIR and LLVM" ON) +option(FLANG_BUILD_NEW_DRIVER "Build the flang compiler driver" OFF) # Flang requires C++17. set(CMAKE_CXX_STANDARD 17) @@ -61,6 +62,12 @@ get_filename_component(LLVM_DIR_ABSOLUTE ${LLVM_DIR} REALPATH) list(APPEND CMAKE_MODULE_PATH ${LLVM_DIR_ABSOLUTE}) + if(FLANG_BUILD_NEW_DRIVER) + # TODO: Remove when libclangDriver is lifted out of Clang + list(APPEND CMAKE_MODULE_PATH ${CLANG_DIR}) + find_package(Clang REQUIRED HINTS "${CLANG_DIR}") + endif() + # If LLVM links to zlib we need the imported targets so we can too. if(LLVM_ENABLE_ZLIB) find_package(ZLIB REQUIRED) @@ -200,6 +207,21 @@ endif() endif() +if(FLANG_BUILD_NEW_DRIVER) + # TODO: Remove when libclangDriver is lifted out of Clang + if(FLANG_STANDALONE_BUILD) + set(CLANG_INCLUDE_DIR ${CLANG_INCLUDE_DIRS} ) + # No need to specify TableGen output dir as that's embedded in CLANG_DIR + else() + set(CLANG_INCLUDE_DIR ${LLVM_MAIN_SRC_DIR}/../clang/include ) + # Specify TableGen output dir for things like DiagnosticCommonKinds.inc, + # DiagnosticDriverKinds.inc (required for reporting diagnostics) + set(CLANG_TABLEGEN_OUTPUT_DIR ${CMAKE_BINARY_DIR}/tools/clang/include) + include_directories(SYSTEM ${CLANG_TABLEGEN_OUTPUT_DIR}) + endif() + include_directories(SYSTEM ${CLANG_INCLUDE_DIR}) +endif() + if(LINK_WITH_FIR) # tco tool and FIR lib output directories if(FLANG_STANDALONE_BUILD) diff --git a/flang/README.md b/flang/README.md --- a/flang/README.md +++ b/flang/README.md @@ -143,6 +143,21 @@ cmake -DLLVM_DIR=$LLVM -DMLIR_DIR=$MLIR ~/flang/src make ``` + +### Build The New Flang Driver +The new Flang driver, `flang-new`, is currently under active development and +should be considered as an experimental feature. For this reason it is disabled +by default. This will change once the new driver replaces the _throwaway_ +driver, `flang`. + +In order to build the new driver, add `-DBUILD_FLANG_NEW_DRIVER=ON` to your +CMake invocation line. Additionally, when building out-of-tree, use `CLANG_DIR` +(similarly to `LLVM_DIR` and `MLIR_DIR`) to find the installed Clang +components. + +**Note:** `CLANG_DIR` is only required when building the new Flang driver, +which currently depends on Clang. + # How to Run Tests Flang supports 2 different categories of tests diff --git a/flang/include/flang/Frontend/CompilerInstance.h b/flang/include/flang/Frontend/CompilerInstance.h new file mode 100644 --- /dev/null +++ b/flang/include/flang/Frontend/CompilerInstance.h @@ -0,0 +1,105 @@ +//===-- CompilerInstance.h - Flang Compiler Instance ------------*- 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 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_FLANG_FRONTEND_COMPILERINSTANCE_H +#define LLVM_FLANG_FRONTEND_COMPILERINSTANCE_H + +#include "flang/Frontend/CompilerInvocation.h" + +#include +#include + +namespace Fortran::frontend { + +class CompilerInstance { + + /// The options used in this compiler instance. + std::shared_ptr invocation_; + + /// The diagnostics engine instance. + llvm::IntrusiveRefCntPtr diagnostics_; + +public: + explicit CompilerInstance(); + + ~CompilerInstance(); + CompilerInvocation &GetInvocation() { + assert(invocation_ && "Compiler instance has no invocation!"); + return *invocation_; + }; + + /// } + /// @name Forwarding Methods + /// { + + clang::DiagnosticOptions &GetDiagnosticOpts() { + return invocation_->GetDiagnosticOpts(); + } + const clang::DiagnosticOptions &GetDiagnosticOpts() const { + return invocation_->GetDiagnosticOpts(); + } + + FrontendOptions &GetFrontendOpts() { return invocation_->GetFrontendOpts(); } + const FrontendOptions &GetFrontendOpts() const { + return invocation_->GetFrontendOpts(); + } + + /// } + /// @name Diagnostics Engine + /// { + + bool HasDiagnostics() const { return diagnostics_ != nullptr; } + + /// Get the current diagnostics engine. + clang::DiagnosticsEngine &GetDiagnostics() const { + assert(diagnostics_ && "Compiler instance has no diagnostics!"); + return *diagnostics_; + } + + /// SetDiagnostics - Replace the current diagnostics engine. + void SetDiagnostics(clang::DiagnosticsEngine *value); + + clang::DiagnosticConsumer &GetDiagnosticClient() const { + assert(diagnostics_ && diagnostics_->getClient() && + "Compiler instance has no diagnostic client!"); + return *diagnostics_->getClient(); + } + + /// Get the current diagnostics engine. + clang::DiagnosticsEngine &getDiagnostics() const { + assert(diagnostics_ && "Compiler instance has no diagnostics!"); + return *diagnostics_; + } + + /// } + /// @name Construction Utility Methods + /// { + + /// Create a DiagnosticsEngine object with a the TextDiagnosticPrinter. + /// + /// If no diagnostic client is provided, this creates a + /// DiagnosticConsumer that is owned by the returned diagnostic + /// object, if using directly the caller is responsible for + /// releasing the returned DiagnosticsEngine's client eventually. + /// + /// \param opts - The diagnostic options; note that the created text + /// diagnostic object contains a reference to these options. + /// + /// \param client If non-NULL, a diagnostic client that will be + /// attached to (and, then, owned by) the returned DiagnosticsEngine + /// object. + /// + /// \return The new object on success, or null on failure. + static clang::IntrusiveRefCntPtr CreateDiagnostics( + clang::DiagnosticOptions *opts, + clang::DiagnosticConsumer *client = nullptr, bool shouldOwnClient = true); + void CreateDiagnostics( + clang::DiagnosticConsumer *client = nullptr, bool shouldOwnClient = true); +}; + +} // end namespace Fortran::frontend +#endif // LLVM_FLANG_FRONTEND_COMPILERINSTANCE_H diff --git a/flang/include/flang/Frontend/CompilerInvocation.h b/flang/include/flang/Frontend/CompilerInvocation.h new file mode 100644 --- /dev/null +++ b/flang/include/flang/Frontend/CompilerInvocation.h @@ -0,0 +1,53 @@ +//===- CompilerInvocation.h - Compiler Invocation Helper Data ---*- 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 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H +#define LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H + +#include "flang/Frontend/FrontendOptions.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticOptions.h" + +namespace Fortran::frontend { +class CompilerInvocationBase { +public: + /// Options controlling the diagnostic engine.$ + llvm::IntrusiveRefCntPtr diagnosticOpts_; + + CompilerInvocationBase(); + CompilerInvocationBase(const CompilerInvocationBase &x); + ~CompilerInvocationBase(); + + clang::DiagnosticOptions &GetDiagnosticOpts() { + return *diagnosticOpts_.get(); + } + const clang::DiagnosticOptions &GetDiagnosticOpts() const { + return *diagnosticOpts_.get(); + } +}; + +class CompilerInvocation : public CompilerInvocationBase { + /// Options controlling the frontend itself. + FrontendOptions frontendOpts_; + +public: + CompilerInvocation() = default; + + FrontendOptions &GetFrontendOpts() { return frontendOpts_; } + const FrontendOptions &GetFrontendOpts() const { return frontendOpts_; } + + /// Create a compiler invocation from a list of input options. + /// \returns true on success. + /// \returns false if an error was encountered while parsing the arguments + /// \param [out] res - The resulting invocation. + static bool CreateFromArgs(CompilerInvocation &res, + llvm::ArrayRef commandLineArgs, + clang::DiagnosticsEngine &diags); +}; + +} // end namespace Fortran::frontend +#endif // LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h new file mode 100644 --- /dev/null +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -0,0 +1,58 @@ +//===- FrontendOptions.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 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_FLANG_FRONTEND_FRONTENDOPTIONS_H +#define LLVM_FLANG_FRONTEND_FRONTENDOPTIONS_H + +#include +#include +namespace Fortran::frontend { + +enum class Language : uint8_t { + Unknown, + + /// LLVM IR: we accept this so that we can run the optimizer on it, + /// and compile it to assembly or object code. + LLVM_IR, + + ///@{ Languages that the frontend can parse and compile. + Fortran, + ///@} +}; + +/// The kind of a file that we've been handed as an input. +class InputKind { +private: + Language lang_; + +public: + /// The input file format. + enum Format { Source, ModuleMap, Precompiled }; + + constexpr InputKind(Language l = Language::Unknown) : lang_(l) {} + + Language GetLanguage() const { return static_cast(lang_); } + + /// Is the input kind fully-unknown? + bool IsUnknown() const { return lang_ == Language::Unknown; } +}; + +/// FrontendOptions - Options for controlling the behavior of the frontend. +class FrontendOptions { +public: + /// Show the -help text. + unsigned showHelp_ : 1; + + /// Show the -version text. + unsigned showVersion_ : 1; + +public: + FrontendOptions() : showHelp_(false), showVersion_(false) {} +}; +} // namespace Fortran::frontend + +#endif // LLVM_FLANG_FRONTEND_FRONTENDOPTIONS_H diff --git a/flang/include/flang/FrontendTool/Utils.h b/flang/include/flang/FrontendTool/Utils.h new file mode 100644 --- /dev/null +++ b/flang/include/flang/FrontendTool/Utils.h @@ -0,0 +1,29 @@ +//===--- Utils.h - Misc utilities for the flang front-end --------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This header contains miscellaneous utilities for various front-end actions +// which were split from Frontend to minimise Frontend's dependencies. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FLANG_FRONTENDTOOL_UTILS_H +#define LLVM_FLANG_FRONTENDTOOL_UTILS_H + +namespace Fortran::frontend { + +class CompilerInstance; + +/// ExecuteCompilerInvocation - Execute the given actions described by the +/// compiler invocation object in the given compiler instance. +/// +/// \return - True on success. +bool ExecuteCompilerInvocation(CompilerInstance *flang); + +} // end namespace Fortran::frontend + +#endif // LLVM_FLANG_FRONTENDTOOL_UTILS_H diff --git a/flang/lib/CMakeLists.txt b/flang/lib/CMakeLists.txt --- a/flang/lib/CMakeLists.txt +++ b/flang/lib/CMakeLists.txt @@ -5,6 +5,11 @@ add_subdirectory(Parser) add_subdirectory(Semantics) +if(FLANG_BUILD_NEW_DRIVER) + add_subdirectory(Frontend) + add_subdirectory(FrontendTool) +endif() + if(LINK_WITH_FIR) add_subdirectory(Optimizer) endif() diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/lib/Frontend/CMakeLists.txt @@ -0,0 +1,16 @@ +add_flang_library(flangFrontend + CompilerInstance.cpp + CompilerInvocation.cpp + FrontendOptions.cpp + + LINK_LIBS + clangBasic + clangDriver + # TODO: Added to re-use clang's TextDiagnosticBuffer & TextDiagnosticPrinter. + # Add a custom implementation for Flang and remove this dependency. + clangFrontend + + LINK_COMPONENTS + Option + Support +) diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Frontend/CompilerInstance.cpp @@ -0,0 +1,42 @@ +//===--- CompilerInstance.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/CompilerInvocation.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "llvm/Support/raw_ostream.h" + +using namespace Fortran::frontend; + +CompilerInstance::CompilerInstance() : invocation_(new CompilerInvocation()) {} + +CompilerInstance::~CompilerInstance() = default; + +void CompilerInstance::CreateDiagnostics( + clang::DiagnosticConsumer *client, bool shouldOwnClient) { + diagnostics_ = + CreateDiagnostics(&GetDiagnosticOpts(), client, shouldOwnClient); +} + +clang::IntrusiveRefCntPtr +CompilerInstance::CreateDiagnostics(clang::DiagnosticOptions *opts, + clang::DiagnosticConsumer *client, bool shouldOwnClient) { + clang::IntrusiveRefCntPtr diagID( + new clang::DiagnosticIDs()); + clang::IntrusiveRefCntPtr diags( + new clang::DiagnosticsEngine(diagID, opts)); + + // Create the diagnostic client for reporting errors or for + // implementing -verify. + if (client) { + diags->setClient(client, shouldOwnClient); + } else { + diags->setClient(new clang::TextDiagnosticPrinter(llvm::errs(), opts)); + } + return diags; +} diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -0,0 +1,115 @@ +//===- CompilerInvocation.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/CompilerInvocation.h" +#include "clang/Basic/AllDiagnostics.h" +#include "clang/Basic/DiagnosticDriver.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Driver/Options.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/raw_ostream.h" + +using namespace Fortran::frontend; + +//===----------------------------------------------------------------------===// +// Initialization. +//===----------------------------------------------------------------------===// +CompilerInvocationBase::CompilerInvocationBase() + : diagnosticOpts_(new clang::DiagnosticOptions()) {} + +CompilerInvocationBase::CompilerInvocationBase(const CompilerInvocationBase &x) + : diagnosticOpts_(new clang::DiagnosticOptions(x.GetDiagnosticOpts())) {} + +CompilerInvocationBase::~CompilerInvocationBase() = default; + +//===----------------------------------------------------------------------===// +// Deserialization (from args) +//===----------------------------------------------------------------------===// +static InputKind ParseFrontendArgs(FrontendOptions &opts, + llvm::opt::ArgList &args, clang::DiagnosticsEngine &diags) { + // Identify the action (i.e. opts.ProgramAction) + if (const llvm::opt::Arg *a = + args.getLastArg(clang::driver::options::OPT_Action_Group)) { + switch (a->getOption().getID()) { + default: { + llvm_unreachable("Invalid option in group!"); + } + // TODO: + // case clang::driver::options::OPT_E: + // case clang::driver::options::OPT_emit_obj: + // case calng::driver::options::OPT_emit_llvm: + // case clang::driver::options::OPT_emit_llvm_only: + // case clang::driver::options::OPT_emit_codegen_only: + // case clang::driver::options::OPT_emit_module: + // (...) + } + } + + opts.showHelp_ = args.hasArg(clang::driver::options::OPT_help); + opts.showVersion_ = args.hasArg(clang::driver::options::OPT_version); + + // Get the input kind (from the value passed via `-x`) + InputKind dashX(Language::Unknown); + if (const llvm::opt::Arg *a = + args.getLastArg(clang::driver::options::OPT_x)) { + llvm::StringRef XValue = a->getValue(); + // Principal languages. + dashX = llvm::StringSwitch(XValue) + .Case("f90", Language::Fortran) + .Default(Language::Unknown); + + // Some special cases cannot be combined with suffixes. + if (dashX.IsUnknown()) + dashX = llvm::StringSwitch(XValue) + .Case("ir", Language::LLVM_IR) + .Default(Language::Unknown); + + if (dashX.IsUnknown()) + diags.Report(clang::diag::err_drv_invalid_value) + << a->getAsString(args) << a->getValue(); + } + + return dashX; +} + +bool CompilerInvocation::CreateFromArgs(CompilerInvocation &res, + llvm::ArrayRef commandLineArgs, + clang::DiagnosticsEngine &diags) { + + bool success = true; + + // Parse the arguments + const llvm::opt::OptTable &opts = clang::driver::getDriverOptTable(); + const unsigned includedFlagsBitmask = + clang::driver::options::FC1Option; + unsigned missingArgIndex, missingArgCount; + llvm::opt::InputArgList args = opts.ParseArgs( + commandLineArgs, missingArgIndex, missingArgCount, includedFlagsBitmask); + + // Issue errors on unknown arguments + for (const auto *a : args.filtered(clang::driver::options::OPT_UNKNOWN)) { + auto argString = a->getAsString(args); + std::string nearest; + if (opts.findNearest(argString, nearest, includedFlagsBitmask) > 1) + diags.Report(clang::diag::err_drv_unknown_argument) << argString; + else + diags.Report(clang::diag::err_drv_unknown_argument_with_suggestion) + << argString << nearest; + success = false; + } + + // Parse the frontend args + ParseFrontendArgs(res.GetFrontendOpts(), args, diags); + + return success; +} diff --git a/flang/lib/Frontend/FrontendOptions.cpp b/flang/lib/Frontend/FrontendOptions.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/Frontend/FrontendOptions.cpp @@ -0,0 +1,9 @@ +//===- FrontendOptions.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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/FrontendOptions.h" diff --git a/flang/lib/FrontendTool/CMakeLists.txt b/flang/lib/FrontendTool/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/lib/FrontendTool/CMakeLists.txt @@ -0,0 +1,11 @@ +add_flang_library(flangFrontendTool + ExecuteCompilerInvocation.cpp + + LINK_LIBS + clangBasic + clangDriver + + LINK_COMPONENTS + Option + Support +) diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp new file mode 100644 --- /dev/null +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -0,0 +1,39 @@ +//===--- ExecuteCompilerInvocation.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 +// +//===----------------------------------------------------------------------===// +// +// This file holds ExecuteCompilerInvocation(). It is split into its own file to +// minimize the impact of pulling in essentially everything else in Flang. +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/CompilerInstance.h" +#include "clang/Driver/Options.h" +#include "llvm/Option/OptTable.h" +#include "llvm/Support/CommandLine.h" + +namespace Fortran::frontend { +bool ExecuteCompilerInvocation(CompilerInstance *flang) { + // Honor -help. + if (flang->GetFrontendOpts().showHelp_) { + clang::driver::getDriverOptTable().PrintHelp(llvm::outs(), + "flang-new -fc1 [options] file...", "LLVM 'Flang' Compiler", + /*Include=*/clang::driver::options::FlangOption, + /*Exclude=*/0, /*ShowAllAliases=*/false); + return true; + } + + // Honor -version. + if (flang->GetFrontendOpts().showVersion_) { + llvm::cl::PrintVersionMessage(); + return true; + } + + return true; +} + +} // namespace Fortran::frontend diff --git a/flang/test/CMakeLists.txt b/flang/test/CMakeLists.txt --- a/flang/test/CMakeLists.txt +++ b/flang/test/CMakeLists.txt @@ -41,6 +41,10 @@ list(APPEND FLANG_TEST_DEPENDS tco) endif() +if (FLANG_BUILD_NEW_DRIVER) + list(APPEND FLANG_TEST_DEPENDS flang-new) +endif() + if (FLANG_INCLUDE_TESTS) if (FLANG_GTEST_AVAIL) list(APPEND FLANG_TEST_DEPENDS FlangUnitTests) diff --git a/flang/test/Flang-Driver/driver-error-cc1.c b/flang/test/Flang-Driver/driver-error-cc1.c new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/driver-error-cc1.c @@ -0,0 +1,7 @@ +// RUN: not %flang-new %s 2>&1 | FileCheck %s + +// REQUIRES: new-flang-driver + +// C files are currently not supported (i.e. `flang -cc1`) + +// CHECK:error: unknown integrated tool '-cc1'. Valid tools include '-fc1'. diff --git a/flang/test/Flang-Driver/driver-error-cc1.cpp b/flang/test/Flang-Driver/driver-error-cc1.cpp new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/driver-error-cc1.cpp @@ -0,0 +1,7 @@ +// RUN: not %flang-new %s 2>&1 | FileCheck %s + +// REQUIRES: new-flang-driver + +// C++ files are currently not supported (i.e. `flang -cc1`) + +// CHECK:error: unknown integrated tool '-cc1'. Valid tools include '-fc1'. diff --git a/flang/test/Flang-Driver/driver-help.f90 b/flang/test/Flang-Driver/driver-help.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/driver-help.f90 @@ -0,0 +1,13 @@ +! RUN: %flang-new -help 2>&1 | FileCheck %s +! RUN: %flang-new -fc1 -help 2>&1 | FileCheck %s +! RUN: not %flang-new -helps 2>&1 | FileCheck %s --check-prefix=ERROR + +! REQUIRES: new-flang-driver + +! CHECK:USAGE: flang-new +! CHECK-EMPTY: +! CHECK-NEXT:OPTIONS: +! CHECK-NEXT: -help Display available options +! CHECK-NEXT: --version Print version information + +! ERROR: error: unknown argument '-helps'; did you mean '-help' diff --git a/flang/test/Flang-Driver/driver-version.f90 b/flang/test/Flang-Driver/driver-version.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/driver-version.f90 @@ -0,0 +1,11 @@ +! RUN: %flang-new --version 2>&1 | FileCheck %s +! RUN: not %flang-new --versions 2>&1 | FileCheck %s --check-prefix=ERROR + +! REQUIRES: new-flang-driver + +! CHECK:flang-new version +! CHECK-NEXT:Target: +! CHECK-NEXT:Thread model: +! CHECK-NEXT:InstalledDir: + +! ERROR: error: unsupported option '--versions'; did you mean '--version'? diff --git a/flang/test/Flang-Driver/emit-obj.f90 b/flang/test/Flang-Driver/emit-obj.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/emit-obj.f90 @@ -0,0 +1,17 @@ +! RUN: not %flang-new %s 2>&1 | FileCheck %s --check-prefix=ERROR-IMPLICIT +! RUN: not %flang-new -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR-EXPLICIT +! RUN: not %flang-new -fc1 -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR-FC1 + +! REQUIRES: new-flang-driver + +! By default (e.g. when no options like `-E` are passed) flang-new +! creates a job that corresponds to `-emit-obj`. This option/action is +! not yet supported. Verify that this is correctly reported as error. + +! ERROR-IMPLICIT: error: unknown argument: '-triple' +! ERROR-IMPLICIT: error: unknown argument: '-emit-obj' +! ERROR-IMPLICIT: error: unknown argument: '-o' + +! ERROR-EXPLICIT: error: unknown argument: '-o' + +! ERROR-FC1: error: unknown argument: '-emit-obj' diff --git a/flang/test/Flang-Driver/missing-input.f90 b/flang/test/Flang-Driver/missing-input.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Flang-Driver/missing-input.f90 @@ -0,0 +1,5 @@ +! RUN: not %flang-new 2>&1 | FileCheck %s + +! REQUIRES: new-flang-driver + +! CHECK: error: no input files 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 @@ -25,7 +25,7 @@ config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell) # suffixes: A list of file extensions to treat as test files. -config.suffixes = ['.f', '.F', '.ff', '.FOR', '.for', '.f77', '.f90', '.F90', +config.suffixes = ['.c', '.cpp', '.f', '.F', '.ff', '.FOR', '.for', '.f77', '.f90', '.F90', '.ff90', '.f95', '.F95', '.ff95', '.fpp', '.FPP', '.cuf', '.CUF', '.f18', '.F18', '.fir'] @@ -38,6 +38,13 @@ # directories. config.excludes = ['Inputs', 'CMakeLists.txt', 'README.txt', 'LICENSE.txt'] +# If the new Flang driver is enabled, add the corresponding feature to +# config. Otherwise, exclude the corresponding test directory. +if config.include_flang_new_driver_test: + config.available_features.add('new-flang-driver') +else: + config.excludes.append('Flang-Driver') + # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) @@ -63,6 +70,9 @@ unresolved='fatal') ] +if config.include_flang_new_driver_test: + tools.append(ToolSubst('%flang-new', command=FindTool('flang-new'), unresolved='fatal')) + if config.flang_standalone_build: llvm_config.add_tool_substitutions(tools, [config.flang_llvm_tools_dir]) else: 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 @@ -11,6 +11,11 @@ config.python_executable = "@PYTHON_EXECUTABLE@" config.flang_standalone_build = @FLANG_STANDALONE_BUILD@ +# Control the regression test for flang-new driver +import lit.util +config.include_flang_new_driver_test = \ + lit.util.pythonize_bool("@FLANG_BUILD_NEW_DRIVER@") + # Support substitution of the tools_dir with user parameters. This is # used when we can't determine the tool dir at configuration time. try: diff --git a/flang/tools/CMakeLists.txt b/flang/tools/CMakeLists.txt --- a/flang/tools/CMakeLists.txt +++ b/flang/tools/CMakeLists.txt @@ -7,6 +7,9 @@ #===------------------------------------------------------------------------===# add_subdirectory(f18) +if(FLANG_BUILD_NEW_DRIVER) + add_subdirectory(flang-driver) +endif() if(LINK_WITH_FIR) add_subdirectory(tco) endif() diff --git a/flang/tools/flang-driver/CMakeLists.txt b/flang/tools/flang-driver/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/tools/flang-driver/CMakeLists.txt @@ -0,0 +1,25 @@ +# Infrastructure to build flang driver entry point. Flang driver depends on +# LLVM libraries. + +# Set your project compile flags. +link_directories(${LLVM_LIBRARY_DIR}) + +add_flang_tool(flang-new + driver.cpp + fc1_main.cpp +) + +# Link against LLVM and Clang libraries +target_link_libraries(flang-new + PRIVATE + ${LLVM_COMMON_LIBS} + flangFrontend + flangFrontendTool + clangDriver + clangBasic + LLVMSupport + LLVMTarget + LLVMOption +) + +install(TARGETS flang-new DESTINATION bin) diff --git a/flang/tools/flang-driver/driver.cpp b/flang/tools/flang-driver/driver.cpp new file mode 100644 --- /dev/null +++ b/flang/tools/flang-driver/driver.cpp @@ -0,0 +1,129 @@ +//===-- driver.cpp - Flang Driver -----------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the flang driver; it is a thin wrapper +// for functionality in the Driver flang library. +// +//===----------------------------------------------------------------------===// +#include "clang/Driver/Driver.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/DiagnosticIDs.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/Compilation.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/VirtualFileSystem.h" + +// main frontend method. Lives inside fc1_main.cpp +extern int fc1_main(llvm::ArrayRef argv, const char *argv0); + +std::string GetExecutablePath(const char *argv0) { + // This just needs to be some symbol in the binary + void *p = (void *)(intptr_t)GetExecutablePath; + return llvm::sys::fs::getMainExecutable(argv0, p); +} + +// This lets us create the DiagnosticsEngine with a properly-filled-out +// DiagnosticOptions instance +static clang::DiagnosticOptions *CreateAndPopulateDiagOpts( + llvm::ArrayRef argv) { + auto *diagOpts = new clang::DiagnosticOptions; + return diagOpts; +} + +static int ExecuteFC1Tool(llvm::SmallVectorImpl &argV) { + llvm::StringRef tool = argV[1]; + if (tool == "-fc1") + return fc1_main(makeArrayRef(argV).slice(2), argV[0]); + + // Reject unknown tools. + // ATM it only supports fc1. Any fc1[*] is rejected. + llvm::errs() << "error: unknown integrated tool '" << tool << "'. " + << "Valid tools include '-fc1'.\n"; + return 1; +} + +int main(int argc_, const char **argv_) { + + // Initialize variables to call the driver + llvm::InitLLVM x(argc_, argv_); + llvm::SmallVector argv(argv_, argv_ + argc_); + + clang::driver::ParsedClangName targetandMode("flang", "--driver-mode=flang"); + std::string driverPath = GetExecutablePath(argv[0]); + + // Check if flang-new is in the frontend mode + auto firstArg = std::find_if( + argv.begin() + 1, argv.end(), [](const char *a) { return a != nullptr; }); + if (firstArg != argv.end()) { + if (llvm::StringRef(argv[1]).startswith("-cc1")) { + llvm::errs() << "error: unknown integrated tool '" << argv[1] << "'. " + << "Valid tools include '-fc1'.\n"; + return 1; + } + // Call flang-new frontend + if (llvm::StringRef(argv[1]).startswith("-fc1")) { + return ExecuteFC1Tool(argv); + } + } + + // Not in the frontend mode - continue in the compiler driver mode. + + // Create DiagnosticsEngine for the compiler driver + llvm::IntrusiveRefCntPtr diagOpts = + CreateAndPopulateDiagOpts(argv); + llvm::IntrusiveRefCntPtr diagID( + new clang::DiagnosticIDs()); + clang::TextDiagnosticPrinter *diagClient = + new clang::TextDiagnosticPrinter(llvm::errs(), &*diagOpts); + clang::DiagnosticsEngine diags(diagID, &*diagOpts, diagClient); + + // Prepare the driver + clang::driver::Driver theDriver(driverPath, + llvm::sys::getDefaultTargetTriple(), diags, "flang LLVM compiler"); + theDriver.setTargetAndMode(targetandMode); + std::unique_ptr c( + theDriver.BuildCompilation(argv)); + llvm::SmallVector, 4> + failingCommands; + + // Run the driver + int res = 1; + bool isCrash = false; + res = theDriver.ExecuteCompilation(*c, failingCommands); + + for (const auto &p : failingCommands) { + int CommandRes = p.first; + const clang::driver::Command *failingCommand = p.second; + if (!res) + res = CommandRes; + + // If result status is < 0 (e.g. when sys::ExecuteAndWait returns -1), + // then the driver command signalled an error. On Windows, abort will + // return an exit code of 3. In these cases, generate additional diagnostic + // information if possible. + isCrash = CommandRes < 0; +#ifdef _WIN32 + IsCrash |= CommandRes == 3; +#endif + if (isCrash) { + theDriver.generateCompilationDiagnostics(*c, *failingCommand); + break; + } + } + + diags.getClient()->finish(); + + // If we have multiple failing commands, we return the result of the first + // failing command. + return res; +} diff --git a/flang/tools/flang-driver/fc1_main.cpp b/flang/tools/flang-driver/fc1_main.cpp new file mode 100644 --- /dev/null +++ b/flang/tools/flang-driver/fc1_main.cpp @@ -0,0 +1,56 @@ +//===-- fc1_main.cpp - Flang FC1 Compiler Frontend ------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This is the entry point to the flang -fc1 functionality, which implements the +// core compiler functionality along with a number of additional tools for +// demonstration and testing purposes. +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/CompilerInvocation.h" +#include "flang/FrontendTool/Utils.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Frontend/TextDiagnosticBuffer.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/OptTable.h" + +#include + +using namespace Fortran::frontend; + +int fc1_main(llvm::ArrayRef argv, const char *argv0) { + // Create CompilerInstance + std::unique_ptr flang(new CompilerInstance()); + + // Create DiagnosticsEngine for the frontend driver + flang->CreateDiagnostics(); + if (!flang->HasDiagnostics()) + return 1; + + // Create CompilerInvocation - use a dedicated instance of DiagnosticsEngine + // for parsing the arguments + llvm::IntrusiveRefCntPtr diagID( + new clang::DiagnosticIDs()); + llvm::IntrusiveRefCntPtr diagOpts = + new clang::DiagnosticOptions(); + clang::TextDiagnosticBuffer *diagsBuffer = new clang::TextDiagnosticBuffer; + clang::DiagnosticsEngine diags(diagID, &*diagOpts, diagsBuffer); + bool success = + CompilerInvocation::CreateFromArgs(flang->GetInvocation(), argv, diags); + + diagsBuffer->FlushDiagnostics(flang->getDiagnostics()); + if (!success) + return 1; + + // Execute the frontend actions. + success = ExecuteCompilerInvocation(flang.get()); + + return !success; +} diff --git a/flang/unittests/CMakeLists.txt b/flang/unittests/CMakeLists.txt --- a/flang/unittests/CMakeLists.txt +++ b/flang/unittests/CMakeLists.txt @@ -22,3 +22,7 @@ add_subdirectory(Evaluate) add_subdirectory(Runtime) add_subdirectory(Lower) + +if (FLANG_BUILD_NEW_DRIVER) + add_subdirectory(Frontend) +endif() diff --git a/flang/unittests/Frontend/CMakeLists.txt b/flang/unittests/Frontend/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/unittests/Frontend/CMakeLists.txt @@ -0,0 +1,10 @@ +add_flang_unittest(FlangFrontendTests + CompilerInstanceTest.cpp +) + +target_link_libraries(FlangFrontendTests + PRIVATE + LLVMSupport + clangBasic + flangFrontend + flangFrontendTool) diff --git a/flang/unittests/Frontend/CompilerInstanceTest.cpp b/flang/unittests/Frontend/CompilerInstanceTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/Frontend/CompilerInstanceTest.cpp @@ -0,0 +1,52 @@ +//===- unittests/Frontend/CompilerInstanceTest.cpp - CI tests -------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "flang/Frontend/CompilerInstance.h" +#include "gtest/gtest.h" +#include "flang/Frontend/CompilerInvocation.h" +#include "clang/Basic/DiagnosticOptions.h" +#include "clang/Driver/Options.h" +#include "clang/Frontend/TextDiagnosticPrinter.h" +#include "llvm/Support/raw_ostream.h" + +#include +using namespace llvm; +using namespace Fortran::frontend; + +namespace { + +TEST(CompilerInstance, AllowDiagnosticLogWithUnownedDiagnosticConsumer) { + // 1. Set-up a basic DiagnosticConsumer + std::string diagnosticOutput; + llvm::raw_string_ostream diagnosticsOS(diagnosticOutput); + auto diagPrinter = std::make_unique( + diagnosticsOS, new clang::DiagnosticOptions()); + + // 2. Create a CompilerInstance (to manage a DiagnosticEngine) + CompilerInstance compInst; + + // 3. Set-up DiagnosticOptions + auto diagOpts = new clang::DiagnosticOptions(); + // Tell the diagnostics engine to emit the diagnostic log to STDERR. This + // ensures that a chained diagnostic consumer is created so that the test can + // exercise the unowned diagnostic consumer in a chained consumer. + diagOpts->DiagnosticLogFile = "-"; + + // 4. Create a DiagnosticEngine with an unowned consumer + IntrusiveRefCntPtr diags = + compInst.CreateDiagnostics(diagOpts, diagPrinter.get(), + /*ShouldOwnClient=*/false); + + // 5. Report a diagnostic + diags->Report(clang::diag::err_expected) << "no crash"; + + // 6. Verify that the reported diagnostic wasn't lost and did end up in the + // output stream + ASSERT_EQ(diagnosticsOS.str(), "error: expected no crash\n"); +} +} // namespace diff --git a/llvm/include/llvm/Option/OptTable.h b/llvm/include/llvm/Option/OptTable.h --- a/llvm/include/llvm/Option/OptTable.h +++ b/llvm/include/llvm/Option/OptTable.h @@ -50,7 +50,7 @@ unsigned ID; unsigned char Kind; unsigned char Param; - unsigned short Flags; + unsigned int Flags; unsigned short GroupID; unsigned short AliasID; const char *AliasArgs;