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 @@ -36,7 +36,8 @@ 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 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -60,6 +60,10 @@ // 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 @@ 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 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]>; @@ -3546,6 +3551,12 @@ def sycl_std_EQ : Joined<["-"], "sycl-std=">, Group, Flags<[CC1Option, NoArgumentUnused, CoreOption]>, 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 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -211,6 +211,11 @@ 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 @@ 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 @@ 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 --- a/clang/lib/Driver/ToolChains/Flang.cpp +++ b/clang/lib/Driver/ToolChains/Flang.cpp @@ -30,12 +30,18 @@ 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 @@ 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 --- a/clang/lib/Driver/Types.cpp +++ b/clang/lib/Driver/Types.cpp @@ -325,10 +325,12 @@ // 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 --- 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 --- 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 @@ /// 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 @@ 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 @@ 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 @@ 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 --- a/flang/include/flang/Frontend/CompilerInvocation.h +++ b/flang/include/flang/Frontend/CompilerInvocation.h @@ -24,7 +24,7 @@ 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 --- /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 --- /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 --- 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 @@ /// 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 @@ 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 @@ /// 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 --- 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 --- 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 @@ clangBasic LINK_LIBS + FortranParser clangBasic clangDriver diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp --- 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 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -87,6 +87,9 @@ 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 @@ } } + 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 @@ << 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 --- /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 --- /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 --- 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 --- a/flang/lib/FrontendTool/CMakeLists.txt +++ b/flang/lib/FrontendTool/CMakeLists.txt @@ -5,6 +5,7 @@ clangBasic LINK_LIBS + flangFrontend clangBasic clangDriver diff --git a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp --- a/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/flang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -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 @@ 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 --- /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 --- 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 --- 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 --- /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 --- /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 --- /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 --- a/flang/test/lit.cfg.py +++ b/flang/test/lit.cfg.py @@ -43,7 +43,7 @@ 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 --- a/flang/tools/flang-driver/fc1_main.cpp +++ b/flang/tools/flang-driver/fc1_main.cpp @@ -56,5 +56,8 @@ // 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 --- 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 @@ clangBasic clangFrontend flangFrontend - flangFrontendTool) + flangFrontendTool + FortranParser + ) diff --git a/flang/unittests/Frontend/CompilerInstanceTest.cpp b/flang/unittests/Frontend/CompilerInstanceTest.cpp --- 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 @@ 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 --- /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