From 4c5906cffd04202387d2f6b50a47d39c0e4f2c0e Mon Sep 17 00:00:00 2001 From: Caroline Concatto Date: Sat, 24 Oct 2020 12:33:19 +0100 Subject: [PATCH] [Flang][Driver] Add infrastructure for basic frontend actions and file I/O MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This patch introduces the dependencies required to read and manage input files provided by the command line option. It also adds the infrastructure to create and write to output files. The output is sent to either stdout or a file (specified with the `-o` flag). Separately, in order to be able to test the code for file I/O, it adds infrastructure to create frontend actions. As a basic testable example, it adds the `InputOutputTest` FrontendAction. The sole purpose of this action is to read a file from the command line and print it either to stdout or the output file. This action is run by using the `-test-io` flag also introduced in this patch (available for `flang-new` and `flang-new -fc1`). With this patch: ``` flang-new -test-io input-file.f90 ``` will read input-file.f90 and print it in the output file. The `InputOutputTest` frontend action has been introduced primarily to facilitate testing. It is hidden from users (i.e. it's only displayed with `--help-hidden`). Currently Clang doesn’t have an equivalent action. `-test-io` is used to trigger the InputOutputTest action in the Flang frontend driver. This patch makes sure that “flang-new” forwards it to “flang-new -fc1" by creating a preprocessor job. However, in Flang.cpp, `-test-io` is passed to “flang-new -fc1” without `-E`. This way we make sure that the preprocessor is _not_ run in the frontend driver. This is the desired behaviour: `-test-io` should only read the input file and print it to the output stream. co-authored-by: Andrzej Warzynski Differential Revision: https://reviews.llvm.org/D87989 --- clang/include/clang/Driver/Options.h | 3 +- clang/include/clang/Driver/Options.td | 17 ++- clang/lib/Driver/Driver.cpp | 12 ++ clang/lib/Driver/ToolChains/Flang.cpp | 16 ++- clang/lib/Driver/Types.cpp | 4 +- clang/test/Driver/immediate-options.c | 4 + flang/include/flang/Frontend/CompilerInstance.h | 121 +++++++++++++++++++-- flang/include/flang/Frontend/CompilerInvocation.h | 2 +- flang/include/flang/Frontend/FrontendAction.h | 101 +++++++++++++++++ flang/include/flang/Frontend/FrontendActions.h | 26 +++++ flang/include/flang/Frontend/FrontendOptions.h | 87 ++++++++++++++- flang/include/flang/FrontendTool/Utils.h | 7 ++ flang/lib/Frontend/CMakeLists.txt | 3 + flang/lib/Frontend/CompilerInstance.cpp | 118 +++++++++++++++++++- flang/lib/Frontend/CompilerInvocation.cpp | 24 ++++ flang/lib/Frontend/FrontendAction.cpp | 61 +++++++++++ flang/lib/Frontend/FrontendActions.cpp | 45 ++++++++ flang/lib/Frontend/FrontendOptions.cpp | 12 ++ flang/lib/FrontendTool/CMakeLists.txt | 1 + .../lib/FrontendTool/ExecuteCompilerInvocation.cpp | 44 +++++++- flang/test/Flang-Driver/driver-help-hidden.f90 | 38 +++++++ flang/test/Flang-Driver/driver-help.f90 | 29 ++++- flang/test/Flang-Driver/emit-obj.f90 | 11 +- flang/test/Frontend/Inputs/hello-world.f90 | 5 + flang/test/Frontend/input-output-file.f90 | 35 ++++++ flang/test/Frontend/multiple-input-files.f90 | 62 +++++++++++ flang/test/lit.cfg.py | 2 +- flang/tools/flang-driver/fc1_main.cpp | 3 + flang/unittests/Frontend/CMakeLists.txt | 5 +- flang/unittests/Frontend/CompilerInstanceTest.cpp | 46 ++++++++ flang/unittests/Frontend/InputOutputTest.cpp | 76 +++++++++++++ 31 files changed, 981 insertions(+), 39 deletions(-) create mode 100644 flang/include/flang/Frontend/FrontendAction.h create mode 100644 flang/include/flang/Frontend/FrontendActions.h create mode 100644 flang/lib/Frontend/FrontendAction.cpp create mode 100644 flang/lib/Frontend/FrontendActions.cpp create mode 100644 flang/test/Flang-Driver/driver-help-hidden.f90 create mode 100644 flang/test/Frontend/Inputs/hello-world.f90 create mode 100644 flang/test/Frontend/input-output-file.f90 create mode 100644 flang/test/Frontend/multiple-input-files.f90 create mode 100644 flang/unittests/Frontend/InputOutputTest.cpp diff --git a/clang/include/clang/Driver/Options.h b/clang/include/clang/Driver/Options.h index 06dd365..ae188ce 100644 --- a/clang/include/clang/Driver/Options.h +++ b/clang/include/clang/Driver/Options.h @@ -36,7 +36,8 @@ enum ClangFlags { LinkOption = (1 << 13), FlangOption = (1 << 14), FC1Option = (1 << 15), - Ignored = (1 << 16), + FlangOnlyOption = (1 << 16), + Ignored = (1 << 17), }; enum ID { diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 0cab3e8..0b9817a 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -60,6 +60,10 @@ def LinkOption : OptionFlag; // flang mode. def FlangOption : OptionFlag; +// FlangOnlyOption - This option should only be used by Flang (i.e. it is not +// available for Clang) +def FlangOnlyOption : OptionFlag; + // FC1Option - This option should be accepted by flang -fc1. def FC1Option : OptionFlag; @@ -2130,8 +2134,8 @@ def gno_embed_source : Flag<["-"], "gno-embed-source">, Group, 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, FC1Option, FlangOption]>, - HelpText<"Display available options">; +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 " "before or after -ibuiltininc. " @@ -2811,7 +2815,8 @@ def nostdincxx : Flag<["-"], "nostdinc++">, Flags<[CC1Option]>, def nostdlib : Flag<["-"], "nostdlib">, Group; def nostdlibxx : Flag<["-"], "nostdlib++">; def object : Flag<["-"], "object">; -def o : JoinedOrSeparate<["-"], "o">, Flags<[DriverOption, RenderAsInput, CC1Option, CC1AsOption]>, +def o : JoinedOrSeparate<["-"], "o">, Flags<[DriverOption, RenderAsInput, + CC1Option, CC1AsOption, FC1Option, FlangOption]>, HelpText<"Write output to ">, MetaVarName<"">; def pagezero__size : JoinedOrSeparate<["-"], "pagezero_size">; def pass_exit_codes : Flag<["-", "--"], "pass-exit-codes">, Flags<[Unsupported]>; @@ -3547,6 +3552,12 @@ def sycl_std_EQ : Joined<["-"], "sycl-std=">, Group, Flags<[CC1Optio HelpText<"SYCL language standard to compile for.">, Values<"2017, 121, 1.2.1, sycl-1.2.1">; //===----------------------------------------------------------------------===// +// FlangOption and FC1 Options +//===----------------------------------------------------------------------===// +def test_io : Flag<["-"], "test-io">, Flags<[HelpHidden, FlangOption, FC1Option, FlangOnlyOption]>, Group, + HelpText<"Run the InputOuputTest action. Use for development and testing only.">; + +//===----------------------------------------------------------------------===// // CC1 Options //===----------------------------------------------------------------------===// diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 6f2a030..aaa6666 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -211,6 +211,11 @@ InputArgList Driver::ParseArgStrings(ArrayRef ArgStrings, std::tie(IncludedFlagsBitmask, ExcludedFlagsBitmask) = getIncludeExcludeOptionFlagMasks(IsClCompatMode); + // Make sure that Flang-only options don't pollute the Clang output + // TODO: Make sure that Clang-only options don't pollute Flang output + if (!IsFlangMode()) + ExcludedFlagsBitmask |= options::FlangOnlyOption; + unsigned MissingArgIndex, MissingArgCount; InputArgList Args = getOpts().ParseArgs(ArgStrings, MissingArgIndex, MissingArgCount, @@ -1573,6 +1578,8 @@ void Driver::PrintHelp(bool ShowHidden) const { if (IsFlangMode()) IncludedFlagsBitmask |= options::FlangOption; + else + ExcludedFlagsBitmask |= options::FlangOnlyOption; std::string Usage = llvm::formatv("{0} [options] file...", Name).str(); getOpts().PrintHelp(llvm::outs(), Usage.c_str(), DriverTitle.c_str(), @@ -1628,6 +1635,11 @@ void Driver::HandleAutocompletions(StringRef PassedFlags) const { unsigned int DisableFlags = options::NoDriverOption | options::Unsupported | options::Ignored; + // Make sure that Flang-only options don't pollute the Clang output + // TODO: Make sure that Clang-only options don't pollute Flang output + if (!IsFlangMode()) + DisableFlags |= options::FlangOnlyOption; + // Distinguish "--autocomplete=-someflag" and "--autocomplete=-someflag," // because the latter indicates that the user put space before pushing tab // which should end up in a file completion. diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp index f8633b9..6d79fbe 100644 --- a/clang/lib/Driver/ToolChains/Flang.cpp +++ b/clang/lib/Driver/ToolChains/Flang.cpp @@ -30,12 +30,18 @@ void Flang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-fc1"); - CmdArgs.push_back("-triple"); - CmdArgs.push_back(Args.MakeArgString(TripleStr)); - + // TODO: Eventually all actions will require a triple (e.g. `-triple + // aarch64-unknown-linux-gnu`). However, `-triple` is currently not supported + // by `flang-new -fc1`, so we only add it selectively to actions that we + // don't support/execute just yet. if (isa(JA)) { - CmdArgs.push_back("-E"); + if (C.getArgs().hasArg(options::OPT_test_io)) + CmdArgs.push_back("-test-io"); + else + CmdArgs.push_back("-E"); } else if (isa(JA) || isa(JA)) { + CmdArgs.push_back("-triple"); + CmdArgs.push_back(Args.MakeArgString(TripleStr)); if (JA.getType() == types::TY_Nothing) { CmdArgs.push_back("-fsyntax-only"); } else if (JA.getType() == types::TY_AST) { @@ -52,6 +58,8 @@ void Flang::ConstructJob(Compilation &C, const JobAction &JA, assert(false && "Unexpected output type!"); } } else if (isa(JA)) { + CmdArgs.push_back("-triple"); + CmdArgs.push_back(Args.MakeArgString(TripleStr)); CmdArgs.push_back("-emit-obj"); } else { assert(false && "Unexpected action class for Flang tool."); diff --git a/clang/lib/Driver/Types.cpp b/clang/lib/Driver/Types.cpp index 2050dffa..e898334 100644 --- a/clang/lib/Driver/Types.cpp +++ b/clang/lib/Driver/Types.cpp @@ -325,10 +325,12 @@ types::getCompilationPhases(const clang::driver::Driver &Driver, // Filter to compiler mode. When the compiler is run as a preprocessor then // compilation is not an option. // -S runs the compiler in Assembly listing mode. + // -test-io is used by Flang to run InputOutputTest action if (Driver.CCCIsCPP() || DAL.getLastArg(options::OPT_E) || DAL.getLastArg(options::OPT__SLASH_EP) || DAL.getLastArg(options::OPT_M, options::OPT_MM) || - DAL.getLastArg(options::OPT__SLASH_P)) + DAL.getLastArg(options::OPT__SLASH_P) || + DAL.getLastArg(options::OPT_test_io)) LastPhase = phases::Preprocess; // --precompile only runs up to precompilation. diff --git a/clang/test/Driver/immediate-options.c b/clang/test/Driver/immediate-options.c index d7cd6be..723a6fa 100644 --- a/clang/test/Driver/immediate-options.c +++ b/clang/test/Driver/immediate-options.c @@ -3,8 +3,12 @@ // HELP-NOT: ast-dump // HELP-NOT: driver-mode +// Make sure that Flang-only options are not available in Clang +// HELP-NOT: test-io + // RUN: %clang --help-hidden | FileCheck %s -check-prefix=HELP-HIDDEN // HELP-HIDDEN: driver-mode +// HELP-HIDDEN-NOT: test-io // RUN: %clang -dumpversion | FileCheck %s -check-prefix=DUMPVERSION // DUMPVERSION: {{[0-9]+\.[0-9.]+}} diff --git a/flang/include/flang/Frontend/CompilerInstance.h b/flang/include/flang/Frontend/CompilerInstance.h index 298be67..c259be1 100644 --- a/flang/include/flang/Frontend/CompilerInstance.h +++ b/flang/include/flang/Frontend/CompilerInstance.h @@ -9,6 +9,9 @@ #define LLVM_FLANG_FRONTEND_COMPILERINSTANCE_H #include "flang/Frontend/CompilerInvocation.h" +#include "flang/Frontend/FrontendAction.h" +#include "flang/Parser/provenance.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -20,18 +23,68 @@ class CompilerInstance { /// The options used in this compiler instance. std::shared_ptr invocation_; + /// Flang file manager. + std::shared_ptr allSources_; + /// The diagnostics engine instance. llvm::IntrusiveRefCntPtr diagnostics_; + /// Holds information about the output file. + struct OutputFile { + std::string filename_; + OutputFile(std::string inputFilename) + : filename_(std::move(inputFilename)) {} + }; + + /// Output stream that doesn't support seeking (e.g. terminal, pipe). + /// This stream is normally wrapped in buffer_ostream before being passed + /// to users (e.g. via CreateOutputFile). + std::unique_ptr nonSeekStream_; + + /// The list of active output files. + std::list outputFiles_; + + /// Holds the output stream provided by the user. Normally, users of + /// CompilerInstance will call CreateOutputFile to obtain/create an output + /// stream. If they want to provide their own output stream, this field will + /// facilitate this. It is optional and will normally be just a nullptr. + std::unique_ptr outputStream_; + public: explicit CompilerInstance(); ~CompilerInstance(); + + /// @name Compiler Invocation + /// { + CompilerInvocation &GetInvocation() { assert(invocation_ && "Compiler instance has no invocation!"); return *invocation_; }; + /// Replace the current invocation. + void SetInvocation(std::shared_ptr value); + + /// } + /// @name File manager + /// { + + /// Return the current allSources. + Fortran::parser::AllSources &GetAllSources() const { return *allSources_; } + + bool HasAllSources() const { return allSources_ != nullptr; } + + /// } + /// @name High-Level Operations + /// { + + /// Execute the provided action against the compiler's + /// CompilerInvocation object. + /// \param act - The action to execute. + /// \return - True on success. + bool ExecuteAction(FrontendAction &act); + /// } /// @name Forwarding Methods /// { @@ -60,7 +113,7 @@ public: return *diagnostics_; } - /// SetDiagnostics - Replace the current diagnostics engine. + /// Replace the current diagnostics engine. void SetDiagnostics(clang::DiagnosticsEngine *value); clang::DiagnosticConsumer &GetDiagnosticClient() const { @@ -75,23 +128,59 @@ public: return *diagnostics_; } + /// { + /// @name Output Files + /// { + + /// Add an output file onto the list of tracked output files. + /// + /// \param outFile - The output file info. + void AddOutputFile(OutputFile &&outFile); + + /// Clear the output file list. + void ClearOutputFiles(bool eraseFiles); + + /// Create the default output file (based on the invocation's options) and + /// add it to the list of tracked output files. If the name of the output + /// file is not provided, it is derived from the input file. + /// + /// \param binary The mode to open the file in. + /// \param baseInput If the invocation contains no output file name (i.e. + /// outputFile_ in FrontendOptions is empty), the input path + /// name to use for deriving the output path. + /// \param extension The extension to use for output names derived from + /// \p baseInput. + /// \return ostream for the output file or nullptr on error. + std::unique_ptr CreateDefaultOutputFile( + bool binary = true, llvm::StringRef baseInput = "", + llvm::StringRef extension = ""); + + /// Create a new output file + /// + /// \param outputPath The path to the output file. + /// \param error [out] On failure, the error. + /// \param binary The mode to open the file in. + /// \return ostream for the output file or nullptr on error. + std::unique_ptr CreateOutputFile( + llvm::StringRef outputPath, std::error_code &error, bool binary); + /// } /// @name Construction Utility Methods /// { - /// Create a DiagnosticsEngine object with a the TextDiagnosticPrinter. + /// Create a DiagnosticsEngine object /// - /// 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. + /// If no diagnostic client is provided, this method 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. + /// \param client - If non-NULL, a diagnostic client that will be attached to + /// (and optionally, depending on /p shouldOwnClient, owned by) the returned + /// DiagnosticsEngine object. /// /// \return The new object on success, or null on failure. static clang::IntrusiveRefCntPtr CreateDiagnostics( @@ -99,6 +188,20 @@ public: clang::DiagnosticConsumer *client = nullptr, bool shouldOwnClient = true); void CreateDiagnostics( clang::DiagnosticConsumer *client = nullptr, bool shouldOwnClient = true); + + /// } + /// @name Output Stream Methods + /// { + void SetOutputStream(std::unique_ptr outStream) { + outputStream_ = std::move(outStream); + } + + bool IsOutputStreamNull() { return (outputStream_ == nullptr); } + + // Allow the frontend compiler to write in the output stream. + void WriteOutputStream(const std::string &message) { + *outputStream_ << message; + } }; } // end namespace Fortran::frontend diff --git a/flang/include/flang/Frontend/CompilerInvocation.h b/flang/include/flang/Frontend/CompilerInvocation.h index 05f9329..e7764d4 100644 --- a/flang/include/flang/Frontend/CompilerInvocation.h +++ b/flang/include/flang/Frontend/CompilerInvocation.h @@ -24,7 +24,7 @@ bool ParseDiagnosticArgs(clang::DiagnosticOptions &opts, class CompilerInvocationBase { public: - /// Options controlling the diagnostic engine.$ + /// Options controlling the diagnostic engine. llvm::IntrusiveRefCntPtr diagnosticOpts_; CompilerInvocationBase(); diff --git a/flang/include/flang/Frontend/FrontendAction.h b/flang/include/flang/Frontend/FrontendAction.h new file mode 100644 index 0000000..0612168 --- /dev/null +++ b/flang/include/flang/Frontend/FrontendAction.h @@ -0,0 +1,101 @@ +//===- FrontendAction.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Defines the flang::FrontendAction interface. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FLANG_FRONTEND_FRONTENDACTION_H +#define LLVM_FLANG_FRONTEND_FRONTENDACTION_H + +#include "flang/Frontend/FrontendOptions.h" +#include "llvm/Support/Error.h" + +namespace Fortran::frontend { +class CompilerInstance; + +/// Abstract base class for actions which can be performed by the frontend. +class FrontendAction { + FrontendInputFile currentInput_; + CompilerInstance *instance_; + +protected: + /// @name Implementation Action Interface + /// @{ + + /// Callback to run the program action, using the initialized + /// compiler instance. + virtual void ExecuteAction() = 0; + + /// Callback at the end of processing a single input, to determine + /// if the output files should be erased or not. + /// + /// By default it returns true if a compiler error occurred. + virtual bool ShouldEraseOutputFiles(); + + /// @} + +public: + FrontendAction() : instance_(nullptr) {} + virtual ~FrontendAction() = default; + + /// @name Compiler Instance Access + /// @{ + + CompilerInstance &GetCompilerInstance() const { + assert(instance_ && "Compiler instance not registered!"); + return *instance_; + } + + void SetCompilerInstance(CompilerInstance *value) { instance_ = value; } + + /// @} + /// @name Current File Information + /// @{ + + const FrontendInputFile &GetCurrentInput() const { return currentInput_; } + + llvm::StringRef GetCurrentFile() const { + assert(!currentInput_.IsEmpty() && "No current file!"); + return currentInput_.GetFile(); + } + + llvm::StringRef GetCurrentFileOrBufferName() const { + assert(!currentInput_.IsEmpty() && "No current file!"); + return currentInput_.IsFile() + ? currentInput_.GetFile() + : currentInput_.GetBuffer()->getBufferIdentifier(); + } + void SetCurrentInput(const FrontendInputFile ¤tInput); + + /// @} + /// @name Public Action Interface + /// @} + + /// Prepare the action for processing the input file \p input. + /// + /// This is run after the options and frontend have been initialized, + /// but prior to executing any per-file processing. + /// \param ci - The compiler instance this action is being run from. The + /// action may store and use this object. + /// \param input - The input filename and kind. + /// \return True on success; on failure the compilation of this file should + bool BeginSourceFile(CompilerInstance &ci, const FrontendInputFile &input); + + /// Run the action. + llvm::Error Execute(); + + /// Perform any per-file post processing, deallocate per-file + /// objects, and run statistics and output file cleanup code. + void EndSourceFile(); +}; + +} // namespace Fortran::frontend + +#endif // LLVM_FLANG_FRONTEND_FRONTENDACTION_H diff --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h new file mode 100644 index 0000000..c6d9d07 --- /dev/null +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -0,0 +1,26 @@ +//===- FrontendActions.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_FRONTENDACTIONS_H +#define LLVM_FLANG_FRONTEND_FRONTENDACTIONS_H + +#include "flang/Frontend/FrontendAction.h" + +namespace Fortran::frontend { + +//===----------------------------------------------------------------------===// +// Custom Consumer Actions +//===----------------------------------------------------------------------===// + +class InputOutputTestAction : public FrontendAction { + void ExecuteAction() override; +}; + +} // namespace Fortran::frontend + +#endif // LLVM_FLANG_FRONTEND_FRONTENDACTIONS_H diff --git a/flang/include/flang/Frontend/FrontendOptions.h b/flang/include/flang/Frontend/FrontendOptions.h index 474086f..f185704 100644 --- a/flang/include/flang/Frontend/FrontendOptions.h +++ b/flang/include/flang/Frontend/FrontendOptions.h @@ -8,10 +8,41 @@ #ifndef LLVM_FLANG_FRONTEND_FRONTENDOPTIONS_H #define LLVM_FLANG_FRONTEND_FRONTENDOPTIONS_H +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/MemoryBuffer.h" + #include #include + namespace Fortran::frontend { +enum ActionKind { + InvalidAction = 0, + + /// -test-io mode + InputOutputTest, + + // TODO: ADD flags as the Actions are implemented, e.g. + // RunPreprocessor, ParserSyntaxOnly, EmitLLVM, EmitLLVMOnly, + // EmitCodeGenOnly, EmitAssembly, (...) +}; + +inline const char *GetActionKindName(const ActionKind ak) { + switch (ak) { + case InputOutputTest: + return "InputOutputTest"; + default: + return ""; + // TODO: + // case RunPreprocessor: + // case ParserSyntaxOnly: + // case EmitLLVM: + // case EmitLLVMOnly: + // case EmitCodeGenOnly: + // (...) + } +} + enum class Language : uint8_t { Unknown, @@ -19,9 +50,8 @@ enum class Language : uint8_t { /// and compile it to assembly or object code. LLVM_IR, - ///@{ Languages that the frontend can parse and compile. + /// @{ Languages that the frontend can parse and compile. Fortran, - ///@} }; /// The kind of a file that we've been handed as an input. @@ -41,6 +71,43 @@ public: bool IsUnknown() const { return lang_ == Language::Unknown; } }; +/// An input file for the front end. +class FrontendInputFile { + /// The file name, or "-" to read from standard input. + std::string file_; + + /// The input, if it comes from a buffer rather than a file. This object + /// does not own the buffer, and the caller is responsible for ensuring + /// that it outlives any users. + const llvm::MemoryBuffer *buffer_ = nullptr; + + /// The kind of input, atm it contains language + InputKind kind_; + +public: + FrontendInputFile() = default; + FrontendInputFile(llvm::StringRef file, InputKind kind) + : file_(file.str()), kind_(kind) {} + FrontendInputFile(const llvm::MemoryBuffer *buffer, InputKind kind) + : buffer_(buffer), kind_(kind) {} + + InputKind GetKind() const { return kind_; } + + bool IsEmpty() const { return file_.empty() && buffer_ == nullptr; } + bool IsFile() const { return !IsBuffer(); } + bool IsBuffer() const { return buffer_ != nullptr; } + + llvm::StringRef GetFile() const { + assert(IsFile()); + return file_; + } + + const llvm::MemoryBuffer *GetBuffer() const { + assert(IsBuffer() && "Requested buffer_, but it is empty!"); + return buffer_; + } +}; + /// FrontendOptions - Options for controlling the behavior of the frontend. class FrontendOptions { public: @@ -50,8 +117,24 @@ public: /// Show the -version text. unsigned showVersion_ : 1; + /// The input files and their types. + std::vector inputs_; + + /// The output file, if any. + std::string outputFile_; + + /// The frontend action to perform. + frontend::ActionKind programAction_; + public: FrontendOptions() : showHelp_(false), showVersion_(false) {} + + // Return the appropriate input kind for a file extension. For example, + /// "*.f" would return Language::Fortran. + /// + /// \return The input kind for the extension, or Language::Unknown if the + /// extension is not recognized. + static InputKind GetInputKindForExtension(llvm::StringRef extension); }; } // namespace Fortran::frontend diff --git a/flang/include/flang/FrontendTool/Utils.h b/flang/include/flang/FrontendTool/Utils.h index f49c4e6..d62c03d 100644 --- a/flang/include/flang/FrontendTool/Utils.h +++ b/flang/include/flang/FrontendTool/Utils.h @@ -17,6 +17,13 @@ namespace Fortran::frontend { class CompilerInstance; +class FrontendAction; + +/// Construct the FrontendAction of a compiler invocation based on the +/// options specified for the compiler invocation. +/// +/// \return - The created FrontendAction object +std::unique_ptr CreateFrontendAction(CompilerInstance &ci); /// ExecuteCompilerInvocation - Execute the given actions described by the /// compiler invocation object in the given compiler instance. diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt index 3cebc95..22e3859 100644 --- a/flang/lib/Frontend/CMakeLists.txt +++ b/flang/lib/Frontend/CMakeLists.txt @@ -1,6 +1,8 @@ add_flang_library(flangFrontend CompilerInstance.cpp CompilerInvocation.cpp + FrontendAction.cpp + FrontendActions.cpp FrontendOptions.cpp TextDiagnosticPrinter.cpp TextDiagnosticBuffer.cpp @@ -10,6 +12,7 @@ add_flang_library(flangFrontend clangBasic LINK_LIBS + FortranParser clangBasic clangDriver diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp index dd92639..7f86920 100644 --- a/flang/lib/Frontend/CompilerInstance.cpp +++ b/flang/lib/Frontend/CompilerInstance.cpp @@ -9,13 +9,127 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/CompilerInvocation.h" #include "flang/Frontend/TextDiagnosticPrinter.h" +#include "flang/Parser/provenance.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" using namespace Fortran::frontend; -CompilerInstance::CompilerInstance() : invocation_(new CompilerInvocation()) {} +CompilerInstance::CompilerInstance() + : invocation_(new CompilerInvocation()), + allSources_(new Fortran::parser::AllSources()) {} -CompilerInstance::~CompilerInstance() = default; +CompilerInstance::~CompilerInstance() { + assert(outputFiles_.empty() && "Still output files in flight?"); +} + +void CompilerInstance::SetInvocation( + std::shared_ptr value) { + invocation_ = std::move(value); +} + +void CompilerInstance::AddOutputFile(OutputFile &&outFile) { + outputFiles_.push_back(std::move(outFile)); +} + +// Helper method to generate the path of the output file. The following logic +// applies: +// 1. If the user specifies the output file via `-o`, then use that (i.e. +// the outputFilename parameter). +// 2. If the user does not specify the name of the output file, derive it from +// the input file (i.e. inputFilename + extension) +// 3. If the output file is not specified and the input file is `-`, then set +// the output file to `-` as well. +static std::string GetOutputFilePath(llvm::StringRef outputFilename, + llvm::StringRef inputFilename, llvm::StringRef extension) { + + // Output filename _is_ specified. Just use that. + if (!outputFilename.empty()) + return std::string(outputFilename); + + // Output filename _is not_ specified. Derive it from the input file name. + std::string outFile = "-"; + if (!extension.empty() && (inputFilename != "-")) { + llvm::SmallString<128> path(inputFilename); + llvm::sys::path::replace_extension(path, extension); + outFile = std::string(path.str()); + } + + return outFile; +} + +std::unique_ptr +CompilerInstance::CreateDefaultOutputFile( + bool binary, llvm::StringRef baseName, llvm::StringRef extension) { + std::string outputPathName; + std::error_code ec; + + // Get the path of the output file + std::string outputFilePath = + GetOutputFilePath(GetFrontendOpts().outputFile_, baseName, extension); + + // Create the output file + std::unique_ptr os = + CreateOutputFile(outputFilePath, ec, binary); + + // Add the file to the list of tracked output files (provided it was created + // successfully) + if (os) + AddOutputFile(OutputFile(outputPathName)); + + return os; +} + +std::unique_ptr CompilerInstance::CreateOutputFile( + llvm::StringRef outputFilePath, std::error_code &error, bool binary) { + + // Creates the file descriptor for the output file + std::unique_ptr os; + std::string osFile; + if (!os) { + osFile = outputFilePath; + os.reset(new llvm::raw_fd_ostream(osFile, error, + (binary ? llvm::sys::fs::OF_None : llvm::sys::fs::OF_Text))); + if (error) + return nullptr; + } + + // Return the stream corresponding to the output file. + // For non-seekable streams, wrap it in llvm::buffer_ostream first. + if (!binary || os->supportsSeeking()) + return std::move(os); + + assert(!nonSeekStream_ && "The non-seek stream has already been set!"); + auto b = std::make_unique(*os); + nonSeekStream_ = std::move(os); + return std::move(b); +} + +void CompilerInstance::ClearOutputFiles(bool eraseFiles) { + for (OutputFile &of : outputFiles_) + if (!of.filename_.empty() && eraseFiles) + llvm::sys::fs::remove(of.filename_); + + outputFiles_.clear(); + nonSeekStream_.reset(); +} + +bool CompilerInstance::ExecuteAction(FrontendAction &act) { + + // Connect Input to a CompileInstance + for (const FrontendInputFile &fif : GetFrontendOpts().inputs_) { + if (act.BeginSourceFile(*this, fif)) { + if (llvm::Error err = act.Execute()) { + consumeError(std::move(err)); + } + act.EndSourceFile(); + } + } + return true; +} void CompilerInstance::CreateDiagnostics( clang::DiagnosticConsumer *client, bool shouldOwnClient) { diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index aef5e3c..69f1d57 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -87,6 +87,9 @@ static InputKind ParseFrontendArgs(FrontendOptions &opts, default: { llvm_unreachable("Invalid option in group!"); } + case clang::driver::options::OPT_test_io: + opts.programAction_ = InputOutputTest; + break; // TODO: // case clang::driver::options::OPT_E: // case clang::driver::options::OPT_emit_obj: @@ -98,6 +101,7 @@ static InputKind ParseFrontendArgs(FrontendOptions &opts, } } + 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); @@ -122,6 +126,26 @@ static InputKind ParseFrontendArgs(FrontendOptions &opts, << a->getAsString(args) << a->getValue(); } + // Collect the input files and save them in our instance of FrontendOptions. + std::vector inputs = + args.getAllArgValues(clang::driver::options::OPT_INPUT); + opts.inputs_.clear(); + if (inputs.empty()) + // '-' is the default input if none is given. + inputs.push_back("-"); + for (unsigned i = 0, e = inputs.size(); i != e; ++i) { + InputKind ik = dashX; + if (ik.IsUnknown()) { + ik = FrontendOptions::GetInputKindForExtension( + llvm::StringRef(inputs[i]).rsplit('.').second); + if (ik.IsUnknown()) + ik = Language::Unknown; + if (i == 0) + dashX = ik; + } + + opts.inputs_.emplace_back(std::move(inputs[i]), ik); + } return dashX; } diff --git a/flang/lib/Frontend/FrontendAction.cpp b/flang/lib/Frontend/FrontendAction.cpp new file mode 100644 index 0000000..024cc82 --- /dev/null +++ b/flang/lib/Frontend/FrontendAction.cpp @@ -0,0 +1,61 @@ +//===--- FrontendAction.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/FrontendAction.h" +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/FrontendActions.h" +#include "llvm/Support/Errc.h" + +using namespace Fortran::frontend; + +void FrontendAction::SetCurrentInput(const FrontendInputFile ¤tInput) { + this->currentInput_ = currentInput; +} + +// Call this method if BeginSourceFile fails. +// Deallocate compiler instance, input and output descriptors +static void BeginSourceFileCleanUp(FrontendAction &fa, CompilerInstance &ci) { + ci.ClearOutputFiles(/*EraseFiles=*/true); + fa.SetCurrentInput(FrontendInputFile()); + fa.SetCompilerInstance(nullptr); +} + +bool FrontendAction::BeginSourceFile( + CompilerInstance &ci, const FrontendInputFile &realInput) { + + FrontendInputFile input(realInput); + assert(!instance_ && "Already processing a source file!"); + assert(!realInput.IsEmpty() && "Unexpected empty filename!"); + SetCurrentInput(realInput); + SetCompilerInstance(&ci); + if (!ci.HasAllSources()) { + BeginSourceFileCleanUp(*this, ci); + return false; + } + return true; +} + +bool FrontendAction::ShouldEraseOutputFiles() { + return GetCompilerInstance().getDiagnostics().hasErrorOccurred(); +} + +llvm::Error FrontendAction::Execute() { + ExecuteAction(); + return llvm::Error::success(); +} + +void FrontendAction::EndSourceFile() { + CompilerInstance &ci = GetCompilerInstance(); + + // Cleanup the output streams, and erase the output files if instructed by the + // FrontendAction. + ci.ClearOutputFiles(/*EraseFiles=*/ShouldEraseOutputFiles()); + + SetCompilerInstance(nullptr); + SetCurrentInput(FrontendInputFile()); +} diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp new file mode 100644 index 0000000..fd45803 --- /dev/null +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -0,0 +1,45 @@ +//===--- FrontendActions.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/FrontendActions.h" +#include "flang/Common/Fortran-features.h" +#include "flang/Common/default-kinds.h" +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Parser/source.h" +#include "clang/Serialization/PCHContainerOperations.h" + +using namespace Fortran::frontend; + +void InputOutputTestAction::ExecuteAction() { + + // Get the name of the file from FrontendInputFile current. + std::string path{GetCurrentFileOrBufferName()}; + std::string buf; + llvm::raw_string_ostream error_stream{buf}; + bool binaryMode = true; + + // Set/store input file info into CompilerInstance. + CompilerInstance &ci = GetCompilerInstance(); + Fortran::parser::AllSources &allSources{ci.GetAllSources()}; + const Fortran::parser::SourceFile *sf; + sf = allSources.Open(path, error_stream); + llvm::ArrayRef fileContent = sf->content(); + + // Output file descriptor to receive the content of input file. + std::unique_ptr os; + + // Do not write on the output file if using outputStream_. + if (ci.IsOutputStreamNull()) { + os = ci.CreateDefaultOutputFile( + binaryMode, GetCurrentFileOrBufferName(), "txt"); + if (!os) + return; + (*os) << fileContent.data(); + } else { + ci.WriteOutputStream(fileContent.data()); + } +} diff --git a/flang/lib/Frontend/FrontendOptions.cpp b/flang/lib/Frontend/FrontendOptions.cpp index ea5d54a..1757db3 100644 --- a/flang/lib/Frontend/FrontendOptions.cpp +++ b/flang/lib/Frontend/FrontendOptions.cpp @@ -7,3 +7,15 @@ //===----------------------------------------------------------------------===// #include "flang/Frontend/FrontendOptions.h" +#include "llvm/ADT/StringSwitch.h" + +using namespace Fortran::frontend; + +InputKind FrontendOptions::GetInputKindForExtension(llvm::StringRef extension) { + return llvm::StringSwitch(extension) + // TODO: Should match the list in flang/test/lit.cfg.py + // FIXME: Currently this API allows at most 9 items per case. + .Cases("f", "F", "f77", "f90", "F90", "f95", "F95", "ff95", "f18", "F18", + Language::Fortran) + .Default(Language::Unknown); +} diff --git a/flang/lib/FrontendTool/CMakeLists.txt b/flang/lib/FrontendTool/CMakeLists.txt index db03495..65e1dd5 100644 --- a/flang/lib/FrontendTool/CMakeLists.txt +++ b/flang/lib/FrontendTool/CMakeLists.txt @@ -5,6 +5,7 @@ add_flang_library(flangFrontendTool clangBasic LINK_LIBS + flangFrontend clangBasic clangDriver diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index 9699792..171d70b 100644 --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -12,18 +12,52 @@ //===----------------------------------------------------------------------===// #include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/FrontendActions.h" #include "clang/Driver/Options.h" #include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/BuryPointer.h" #include "llvm/Support/CommandLine.h" namespace Fortran::frontend { + +static std::unique_ptr CreateFrontendBaseAction( + CompilerInstance &ci) { + + ActionKind ak = ci.GetFrontendOpts().programAction_; + switch (ak) { + case InputOutputTest: + return std::make_unique(); + break; + default: + break; + // TODO: + // case RunPreprocessor: + // case ParserSyntaxOnly: + // case EmitLLVM: + // case EmitLLVMOnly: + // case EmitCodeGenOnly: + // (...) + } + return 0; +} + +std::unique_ptr CreateFrontendAction(CompilerInstance &ci) { + // Create the underlying action. + std::unique_ptr act = CreateFrontendBaseAction(ci); + if (!act) + return nullptr; + + return act; +} 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::FC1Option, - /*Exclude=*/0, /*ShowAllAliases=*/false); + /*Exclude=*/llvm::opt::DriverFlag::HelpHidden, + /*ShowAllAliases=*/false); return true; } @@ -33,7 +67,13 @@ bool ExecuteCompilerInvocation(CompilerInstance *flang) { return true; } - return true; + // Create and execute the frontend action. + std::unique_ptr act(CreateFrontendAction(*flang)); + if (!act) + return false; + + bool success = flang->ExecuteAction(*act); + return success; } } // namespace Fortran::frontend diff --git a/flang/test/Flang-Driver/driver-help-hidden.f90 b/flang/test/Flang-Driver/driver-help-hidden.f90 new file mode 100644 index 0000000..1a8c739 --- /dev/null +++ b/flang/test/Flang-Driver/driver-help-hidden.f90 @@ -0,0 +1,38 @@ +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! RUN: %flang-new --help-hidden 2>&1 | FileCheck %s +! RUN: not %flang-new -help-hidden 2>&1 | FileCheck %s --check-prefix=ERROR-FLANG + +!---------------------------------------- +! FLANG FRONTEND DRIVER (flang-new -fc1) +!---------------------------------------- +! RUN: not %flang-new -fc1 --help-hidden 2>&1 | FileCheck %s --check-prefix=ERROR-FLANG-FC1 +! RUN: not %flang-new -fc1 -help-hidden 2>&1 | FileCheck %s --check-prefix=ERROR-FLANG-FC1 + +!---------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG DRIVER (flang-new) +!---------------------------------------------------- +! CHECK:USAGE: flang-new +! CHECK-EMPTY: +! CHECK-NEXT:OPTIONS: +! CHECK-NEXT: -fcolor-diagnostics Enable colors in diagnostics +! CHECK-NEXT: -fno-color-diagnostics Disable colors in diagnostics +! CHECK-NEXT: -help Display available options +! CHECK-NEXT: -o Write output to +! CHECK-NEXT: -test-io Run the InputOuputTest action. Use for development and testing only. +! CHECK-NEXT: --version Print version information + +!------------------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG DRIVER (flang-new) +!------------------------------------------------------------- +! ERROR-FLANG: error: unknown argument '-help-hidden'; did you mean '--help-hidden'? + +!------------------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG FRONTEND DRIVER (flang-new -fc1) +!------------------------------------------------------------- +! Frontend driver -help-hidden is not supported +! ERROR-FLANG-FC1: error: unknown argument: '{{.*}}' + diff --git a/flang/test/Flang-Driver/driver-help.f90 b/flang/test/Flang-Driver/driver-help.f90 index aafc563..aecac33 100644 --- a/flang/test/Flang-Driver/driver-help.f90 +++ b/flang/test/Flang-Driver/driver-help.f90 @@ -1,21 +1,40 @@ +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- ! RUN: %flang-new -help 2>&1 | FileCheck %s --check-prefix=HELP -! RUN: %flang-new -fc1 -help 2>&1 | FileCheck %s --check-prefix=HELP-FC1 ! RUN: not %flang-new -helps 2>&1 | FileCheck %s --check-prefix=ERROR -! REQUIRES: new-flang-driver +!---------------------------------------- +! FLANG FRONTEND DRIVER (flang-new -fc1) +!---------------------------------------- +! RUN: %flang-new -fc1 -help 2>&1 | FileCheck %s --check-prefix=HELP-FC1 +! RUN: not %flang-new -fc1 -helps 2>&1 | FileCheck %s --check-prefix=ERROR +!----------------------------- +! EXPECTED OUTPUT (flang-new) +!----------------------------- ! HELP:USAGE: flang-new ! HELP-EMPTY: ! HELP-NEXT:OPTIONS: ! HELP-NEXT: -fcolor-diagnostics Enable colors in diagnostics ! HELP-NEXT: -fno-color-diagnostics Disable colors in diagnostics -! HELP-NEXT: -help Display available options -! HELP-NEXT: --version Print version information +! HELP-NEXT: -help Display available options +! HELP-NEXT: -o Write output to +! HELP-NEXT: --version Print version information +!---------------------------------- +! EXPECTED OUTPUT (flang-new -fc1) +!---------------------------------- ! HELP-FC1:USAGE: flang-new ! HELP-FC1-EMPTY: ! HELP-FC1-NEXT:OPTIONS: ! HELP-FC1-NEXT: -help Display available options +! HELP-FC1-NEXT: -o Write output to ! HELP-FC1-NEXT: --version Print version information -! ERROR: flang-new: error: unknown argument '-helps'; did you mean '-help' +!--------------- +! EXPECTED ERROR +!--------------- +! ERROR: error: unknown argument '-helps'; did you mean '-help' diff --git a/flang/test/Flang-Driver/emit-obj.f90 b/flang/test/Flang-Driver/emit-obj.f90 index 4ddd483..09c1f84 100644 --- a/flang/test/Flang-Driver/emit-obj.f90 +++ b/flang/test/Flang-Driver/emit-obj.f90 @@ -1,5 +1,5 @@ -! 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 %s 2>&1 | FileCheck %s --check-prefix=ERROR +! RUN: not %flang-new -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR ! RUN: not %flang-new -fc1 -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR-FC1 ! REQUIRES: new-flang-driver @@ -8,10 +8,7 @@ ! 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: error: unknown argument: '-triple' +! ERROR: error: unknown argument: '-emit-obj' ! ERROR-FC1: error: unknown argument: '-emit-obj' diff --git a/flang/test/Frontend/Inputs/hello-world.f90 b/flang/test/Frontend/Inputs/hello-world.f90 new file mode 100644 index 0000000..b06ad6a --- /dev/null +++ b/flang/test/Frontend/Inputs/hello-world.f90 @@ -0,0 +1,5 @@ +!This is a test file with a hello world in Fortran +program hello + implicit none + write(*,*) 'Hello world!' +end program hello diff --git a/flang/test/Frontend/input-output-file.f90 b/flang/test/Frontend/input-output-file.f90 new file mode 100644 index 0000000..15705f2 --- /dev/null +++ b/flang/test/Frontend/input-output-file.f90 @@ -0,0 +1,35 @@ +! RUN: rm -rf %S/input-output-file.txt + +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! TEST 1: Print to stdout (implicit) +! RUN: %flang-new -test-io %s 2>&1 | FileCheck %s +! TEST 2: Print to stdout (explicit) +! RUN: %flang-new -test-io -o - %s 2>&1 | FileCheck %s +! TEST 3: Print to a file +! RUN: %flang-new -test-io -o %t %s 2>&1 && FileCheck %s --input-file=%t + +!---------------------------------------- +! FLANG FRONTEND DRIVER (flang-new -fc1) +!---------------------------------------- +! TEST 4: Write to a file (implicit) +! RUN: %flang-new -fc1 -test-io %s 2>&1 && FileCheck %s --input-file=%S/input-output-file.txt +! TEST 5: Write to a file (explicit) +! RUN: %flang-new -fc1 -test-io -o %t %s 2>&1 && FileCheck %s --input-file=%t + + +!----------------------- +! EXPECTED OUTPUT +!----------------------- +! CHECK-LABEL: Program arithmetic +! CHECK-NEXT: Integer :: i, j +! CHECK-NEXT: i = 2; j = 3; i= i * j; +! CHECK-NEXT: End Program arithmetic + +Program arithmetic + Integer :: i, j + i = 2; j = 3; i= i * j; +End Program arithmetic \ No newline at end of file diff --git a/flang/test/Frontend/multiple-input-files.f90 b/flang/test/Frontend/multiple-input-files.f90 new file mode 100644 index 0000000..d091faa --- /dev/null +++ b/flang/test/Frontend/multiple-input-files.f90 @@ -0,0 +1,62 @@ +! RUN: rm -rf %S/multiple-input-files.txt %S/Inputs/hello-world.txt + +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! TEST 1: Both input files are processed (output is printed to stdout) +! RUN: %flang-new -test-io %s %S/Inputs/hello-world.f90 | FileCheck %s -check-prefix=flang-new + +! TEST 2: None of the files is processed (not possible to specify the output file when multiple input files are present) +! RUN: not %flang-new -test-io -o - %S/Inputs/hello-world.f90 %s 2>&1 | FileCheck %s -check-prefix=ERROR +! RUN: not %flang-new -test-io -o %t %S/Inputs/hello-world.f90 %s 2>&1 | FileCheck %s -check-prefix=ERROR + +!---------------------------------------- +! FLANG FRONTEND DRIVER (flang-new -fc1) +!---------------------------------------- +! TEST 3: Both input files are processed +! RUN: %flang-new -fc1 -test-io %S/Inputs/hello-world.f90 %s 2>&1 \ +! RUN: && FileCheck %s --input-file=%S/multiple-input-files.txt -check-prefix=flang-new-FC1-OUTPUT1 + +! TEST 4: Only the last input file is processed +! RUN: %flang-new -fc1 -test-io %S/Inputs/hello-world.f90 %s -o %t 2>&1 \ +! RUN: && FileCheck %s --input-file=%t -check-prefix=flang-new-FC1-OUTPUT1 + +!----------------------- +! EXPECTED OUTPUT +!----------------------- +! TEST 1: By default, `flang-new` prints the output from all input files to +! stdout +! flang-new-LABEL: Program arithmetic +! flang-new-NEXT: Integer :: i, j +! flang-new-NEXT: i = 2; j = 3; i= i * j; +! flang-new-NEXT: End Program arithmetic +! flang-new-NEXT: !This is a test file with a hello world in Fortran +! flang-new-NEXT:program hello +! flang-new-NEXT: implicit none +! flang-new-NEXT: write(*,*) 'Hello world!' +! flang-new-NEXT:end program hello + + +! TEST 2: `-o` does not work for `flang-new` when multiple input files are present +! ERROR:error: cannot specify -o when generating multiple output files + + +! TEST 3 & TEST 4: Unless the output file is specified, `flang-new -fc1` generates one output file for every input file. If an +! output file is specified (with `-o`), then only the last input file is processed. +! flang-new-FC1-OUTPUT1-LABEL: Program arithmetic +! flang-new-FC1-OUTPUT1-NEXT: Integer :: i, j +! flang-new-FC1-OUTPUT1-NEXT: i = 2; j = 3; i= i * j; +! flang-new-FC1-OUTPUT1-NEXT: End Program arithmetic +! flang-new-FC1-OUTPUT1-NEXT: !This is a test file with a hello world in Fortran +! flang-new-FC1-OUTPUT1-NEXT:program hello +! flang-new-FC1-OUTPUT1-NEXT: implicit none +! flang-new-FC1-OUTPUT1-NEXT: write(*,*) 'Hello world!' +! flang-new-FC1-OUTPUT1-NEXT:end program hello + + +Program arithmetic + Integer :: i, j + i = 2; j = 3; i= i * j; +End Program arithmetic diff --git a/flang/test/lit.cfg.py b/flang/test/lit.cfg.py index 21d8530..77b2671 100644 --- a/flang/test/lit.cfg.py +++ b/flang/test/lit.cfg.py @@ -43,7 +43,7 @@ config.excludes = ['Inputs', 'CMakeLists.txt', 'README.txt', 'LICENSE.txt'] if config.include_flang_new_driver_test: config.available_features.add('new-flang-driver') else: - config.excludes.append('Flang-Driver') + config.excludes.append('Flang-Driver','Frontend') # test_source_root: The root path where tests are located. config.test_source_root = os.path.dirname(__file__) diff --git a/flang/tools/flang-driver/fc1_main.cpp b/flang/tools/flang-driver/fc1_main.cpp index 5f7eeb1..ba6552b 100644 --- a/flang/tools/flang-driver/fc1_main.cpp +++ b/flang/tools/flang-driver/fc1_main.cpp @@ -56,5 +56,8 @@ int fc1_main(llvm::ArrayRef argv, const char *argv0) { // Execute the frontend actions. success = ExecuteCompilerInvocation(flang.get()); + // Delete output files to free Compiler Instance + flang->ClearOutputFiles(/*EraseFiles=*/false); + return !success; } diff --git a/flang/unittests/Frontend/CMakeLists.txt b/flang/unittests/Frontend/CMakeLists.txt index fdccd68..4c22b65 100644 --- a/flang/unittests/Frontend/CMakeLists.txt +++ b/flang/unittests/Frontend/CMakeLists.txt @@ -1,5 +1,6 @@ add_flang_unittest(FlangFrontendTests CompilerInstanceTest.cpp + InputOutputTest.cpp ) target_link_libraries(FlangFrontendTests @@ -7,4 +8,6 @@ target_link_libraries(FlangFrontendTests clangBasic clangFrontend flangFrontend - flangFrontendTool) + flangFrontendTool + FortranParser + ) diff --git a/flang/unittests/Frontend/CompilerInstanceTest.cpp b/flang/unittests/Frontend/CompilerInstanceTest.cpp index 04d581c..932cacd 100644 --- a/flang/unittests/Frontend/CompilerInstanceTest.cpp +++ b/flang/unittests/Frontend/CompilerInstanceTest.cpp @@ -9,6 +9,7 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/TextDiagnosticPrinter.h" #include "clang/Basic/DiagnosticOptions.h" +#include "llvm/Support//FileSystem.h" #include "gtest/gtest.h" @@ -17,6 +18,51 @@ using namespace Fortran::frontend; namespace { +TEST(CompilerInstance, SanityCheckForFileManager) { + const char *inputSource = "InputSourceFile"; + std::string inputFile = "buffer-file-test.f"; + std::error_code ec; + + // 1. Create the input file for the file manager + // AllSources (which is used to manage files inside every compiler instance), + // works with paths. This means that it requires a physical file. Create one. + std::unique_ptr os{ + new llvm::raw_fd_ostream(inputFile, ec, llvm::sys::fs::OF_None)}; + if (ec) + FAIL() << "Failed to create the input file"; + + // Populate the input file with the pre-defined input and flush it. + *(os) << inputSource; + os.reset(); + + // Get the path of the input file + llvm::SmallString<64> cwd; + if (std::error_code ec = llvm::sys::fs::current_path(cwd)) + FAIL() << "Failed to obtain the current working directory"; + std::string testFilePath(cwd.c_str()); + testFilePath += "/" + inputFile; + + // 2. Set up CompilerInstance (i.e. specify the input file) + std::string buf; + llvm::raw_string_ostream error_stream{buf}; + CompilerInstance compInst; + const Fortran::parser::SourceFile *sf = + compInst.GetAllSources().Open(testFilePath, error_stream); + + // 3. Verify the content of the input file + // This is just a sanity check to make sure that CompilerInstance is capable + // of reading input files. + llvm::ArrayRef fileContent = sf->content(); + EXPECT_FALSE(fileContent.size() == 0); + EXPECT_TRUE( + llvm::StringRef(fileContent.data()).startswith("InputSourceFile")); + + // 4. Delete the test file + ec = llvm::sys::fs::remove(inputFile); + if (ec) + FAIL() << "Failed to delete the test file"; +} + TEST(CompilerInstance, AllowDiagnosticLogWithUnownedDiagnosticConsumer) { // 1. Set-up a basic DiagnosticConsumer std::string diagnosticOutput; diff --git a/flang/unittests/Frontend/InputOutputTest.cpp b/flang/unittests/Frontend/InputOutputTest.cpp new file mode 100644 index 0000000..f2f28ae --- /dev/null +++ b/flang/unittests/Frontend/InputOutputTest.cpp @@ -0,0 +1,76 @@ +//===- unittests/Frontend/OutputStreamTest.cpp --- FrontendAction 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 "gtest/gtest.h" +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/CompilerInvocation.h" +#include "flang/Frontend/FrontendOptions.h" +#include "flang/FrontendTool/Utils.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +using namespace Fortran::frontend; + +namespace { + +TEST(FrontendAction, TestInputOutputTestAction) { + std::string inputFile = "io-file-test.f"; + std::error_code ec; + + // 1. Create the input file for the file manager + // AllSources (which is used to manage files inside every compiler instance), + // works with paths. This means that it requires a physical file. Create one. + std::unique_ptr os{ + new llvm::raw_fd_ostream(inputFile, ec, llvm::sys::fs::OF_None)}; + if (ec) + FAIL() << "Failed to create the input file"; + + // Populate the input file with the pre-defined input and flush it. + *(os) << "End Program arithmetic"; + os.reset(); + + // Get the path of the input file + llvm::SmallString<64> cwd; + if (std::error_code ec = llvm::sys::fs::current_path(cwd)) + FAIL() << "Failed to obtain the current working directory"; + std::string testFilePath(cwd.c_str()); + testFilePath += "/" + inputFile; + + // 2. Prepare the compiler (CompilerInvocation + CompilerInstance) + CompilerInstance compInst; + compInst.CreateDiagnostics(); + auto invocation = std::make_shared(); + invocation->GetFrontendOpts().programAction_ = InputOutputTest; + compInst.SetInvocation(std::move(invocation)); + compInst.GetFrontendOpts().inputs_.push_back( + FrontendInputFile(/*File=*/testFilePath, Language::Fortran)); + + // 3. Set-up the output stream. Using output buffer wrapped as an output + // stream, as opposed to an actual file (or a file descriptor). + llvm::SmallVector outputFileBuffer; + std::unique_ptr outputFileStream( + new llvm::raw_svector_ostream(outputFileBuffer)); + compInst.SetOutputStream(std::move(outputFileStream)); + + // 4. Run the earlier defined FrontendAction + bool success = ExecuteCompilerInvocation(&compInst); + + EXPECT_TRUE(success); + EXPECT_TRUE(!outputFileBuffer.empty()); + EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()) + .startswith("End Program arithmetic")); + + // 5. Clear the input and the output files. Since we used an output buffer, + // there are no physical output files to delete. + ec = llvm::sys::fs::remove(inputFile); + if (ec) + FAIL() << "Failed to delete the test file"; + + compInst.ClearOutputFiles(/*EraseFiles=*/false); +} +} // namespace -- 2.7.4