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 @@ -2781,7 +2781,7 @@ 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]>, HelpText<"Write output to ">, MetaVarName<"">; def pagezero__size : JoinedOrSeparate<["-"], "pagezero_size">; def pass_exit_codes : Flag<["-", "--"], "pass-exit-codes">, Flags<[Unsupported]>; @@ -3508,6 +3508,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]>,Group, +HelpText<"Use for Flang development and testing only. Only read and write files.">; + //===----------------------------------------------------------------------===// // 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 @@ -1571,8 +1571,12 @@ if (!ShowHidden) ExcludedFlagsBitmask |= HelpHidden; - if (IsFlangMode()) + if (IsFlangMode()) { IncludedFlagsBitmask |= options::FlangOption; + } else { + ExcludedFlagsBitmask |= options::FlangOption; + ExcludedFlagsBitmask |= options::FC1Option; + } std::string Usage = llvm::formatv("{0} [options] file...", Name).str(); getOpts().PrintHelp(llvm::outs(), Usage.c_str(), DriverTitle.c_str(), 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)); - 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)) { + // TODO: Note that eventually all actions will require a triple + // (e.g. `-triple aarch64-unknown-linux-gnu`). + // However, `-triple` is currently not supported by `flang-new -fc1`, + // FIX IT when actions are supported/executed. + 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,12 @@ assert(false && "Unexpected output type!"); } } else if (isa(JA)) { + // TODO: Note that eventually all actions will require a triple + // (e.g. `-triple aarch64-unknown-linux-gnu`). + // However, `-triple` is currently not supported by `flang-new -fc1`, + // FIX IT when actions are supported/executed. + 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 @@ -2,9 +2,11 @@ // HELP: isystem // HELP-NOT: ast-dump // HELP-NOT: driver-mode +// HEKP-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,70 @@ /// 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 the name of the output file. + /// Helps to go through the list when working with outputFiles_ + 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. + /// Allows to check the output without creating the outputFile_. + /// 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. + /// Note that this routine may write output to 'stderr'. + /// \param act - The action to execute. + /// \return - True on success. + bool ExecuteAction(FrontendAction &act); + /// } /// @name Forwarding Methods /// { @@ -60,7 +115,7 @@ return *diagnostics_; } - /// SetDiagnostics - Replace the current diagnostics engine. + /// Replace the current diagnostics engine. void SetDiagnostics(clang::DiagnosticsEngine *value); clang::DiagnosticConsumer &GetDiagnosticClient() const { @@ -75,6 +130,60 @@ 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); + + /// Return the newly created default output file (from the invocation's + /// options) that was added to the list of tracked output files. + /// + /// \return - Null on error. + std::unique_ptr + CreateDefaultOutputFile(bool binary = true, llvm::StringRef inFile = "", + llvm::StringRef extension = ""); + + /// Function a new output file and _adds_ it to the list of + /// tracked output files inside the compiler instance. + /// + /// \return - Null on error. + std::unique_ptr + CreateOutputFile(llvm::StringRef outputPath, bool binary, + llvm::StringRef inFile, llvm::StringRef extension); + + /// Create a new output file, but does _not_ add to the list of + /// tracked output files inside the compiler instance + /// + /// \param outputPath - If given, the path to the output file. + /// \param error [out] - On failure, the error. + /// \param binary - The mode to open the file in. + /// \param inFile - The input file's name. Could be used to generate the + /// output file name. \param extension - The extension to use for derived + /// output names. \param resultPathName [out] - If given, the result path name + /// will be stored here on success. + std::unique_ptr + CreateOutputFile(llvm::StringRef outputPath, std::error_code &error, + bool binary, llvm::StringRef inFile, + llvm::StringRef extension, std::string *resultPathName); + + 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; + } + /// } /// @name Construction Utility Methods /// { 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 @@ -15,7 +15,7 @@ namespace Fortran::frontend { 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,96 @@ +//===- 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 +// +//===----------------------------------------------------------------------===// + +#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; + +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,22 @@ +//===- 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 { + +class InputOutputTestAction : public FrontendAction { + void ExecuteAction() override; +}; + +} // namespace Fortran::frontend + +#endif // LLVM_FLANG_FRONTEND_FRONTENDACTIONS_H \ No newline at end of file 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,42 @@ #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 { + // This is temporary solution + // Avoids Action = InputOutputTest as option 0 + InvalidAction = 0, + + /// -test-io mode + InputOutputTest, + + /// TODO: ADD flags as the Actions are implemented + // 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, @@ -21,7 +53,10 @@ ///@{ Languages that the frontend can parse and compile. Fortran, - ///@} + Fortran77, + Fortran90, + Fortran95, + FortranF18 }; /// The kind of a file that we've been handed as an input. @@ -41,6 +76,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 +122,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, + /// "f90" would return Language::Fortran90. + /// + /// \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,9 +1,12 @@ add_flang_library(flangFrontend CompilerInstance.cpp CompilerInvocation.cpp + FrontendAction.cpp + FrontendActions.cpp FrontendOptions.cpp LINK_LIBS + FortranParser clangBasic clangDriver # TODO: Added to re-use clang's TextDiagnosticBuffer & TextDiagnosticPrinter. 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 @@ -8,14 +8,122 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/CompilerInvocation.h" +#include "flang/Parser/provenance.h" #include "clang/Frontend/TextDiagnosticPrinter.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)); +} + +std::unique_ptr +CompilerInstance::CreateDefaultOutputFile(bool binary, llvm::StringRef inFile, + llvm::StringRef extension) { + return CreateOutputFile(GetFrontendOpts().outputFile_, binary, inFile, + extension); +} + +std::unique_ptr +CompilerInstance::CreateOutputFile(llvm::StringRef outputPath, bool binary, + llvm::StringRef inFile, + llvm::StringRef extension) { + std::string outputPathName; + std::error_code ec; + + std::unique_ptr os = CreateOutputFile( + outputPath, ec, binary, inFile, extension, &outputPathName); + + AddOutputFile(OutputFile(outputPathName)); + return os; +} + +std::unique_ptr CompilerInstance::CreateOutputFile( + llvm::StringRef outputPath, std::error_code &error, bool binary, + llvm::StringRef inFile, llvm::StringRef extension, + std::string *resultPathName) { + + std::string outFile; + + // Create the name of the output file + if (!outputPath.empty()) { + outFile = std::string(outputPath); + } else if (inFile == "-") { + outFile = "-"; + } else if (!extension.empty()) { + llvm::SmallString<128> path(inFile); + llvm::sys::path::replace_extension(path, extension); + outFile = std::string(path.str()); + } else { + outFile = "-"; + } + + if (resultPathName) + *resultPathName = outFile; + + // Creates the file descriptor for the output file + std::unique_ptr os; + std::string osFile; + if (!os) { + osFile = outFile; + 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 @@ -44,6 +44,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: @@ -55,6 +58,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); @@ -65,7 +69,7 @@ llvm::StringRef XValue = a->getValue(); // Principal languages. dashX = llvm::StringSwitch(XValue) - .Case("f90", Language::Fortran) + .Case("f90", Language::Fortran90) .Default(Language::Unknown); // Some special cases cannot be combined with suffixes. @@ -79,6 +83,26 @@ << a->getAsString(args) << a->getValue(); } + // Save input file to FrontendInputFile with kind and name + // '-' is the default input if none is given. + std::vector inputs = + args.getAllArgValues(clang::driver::options::OPT_INPUT); + opts.inputs_.clear(); + if (inputs.empty()) + 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,62 @@ +//===--- 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/ADT/StringRef.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()); +} \ No newline at end of file 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 compiler instace + 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,19 @@ //===----------------------------------------------------------------------===// #include "flang/Frontend/FrontendOptions.h" +#include "llvm/ADT/StringSwitch.h" + +using namespace Fortran::frontend; + +InputKind FrontendOptions::GetInputKindForExtension(llvm::StringRef extension) { + return llvm::StringSwitch(extension) + // Spreading in cases to cover more extensions as API allows + // at most 9 items per case. + .Cases("ff", "fpp", "FPP", "cuf", "CUF", "fir", "FOR", "for", + Language::Fortran) + .Cases("f", "F", "f77", Language::Fortran77) + .Cases("f90", "F90", ".ff90", Language::Fortran90) + .Cases("f95", "F95", "ff95", Language::Fortran95) + .Cases("f18", "F18", Language::FortranF18) + .Default(Language::Unknown); +} \ No newline at end of file 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 @@ -2,6 +2,7 @@ ExecuteCompilerInvocation.cpp 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,53 @@ //===----------------------------------------------------------------------===// #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::FlangOption, - /*Exclude=*/0, /*ShowAllAliases=*/false); + clang::driver::getDriverOptTable().PrintHelp( + llvm::outs(), "flang-new -fc1 [options] file...", + "LLVM 'Flang' Compiler", + /*Include=*/clang::driver::options::FC1Option, + /*Exclude=*/llvm::opt::DriverFlag::HelpHidden, + /*ShowAllAliases=*/false); return true; } @@ -33,7 +68,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,37 @@ +! 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: -help Display available options + +! CHECK-NEXT: -test-io Use for Flang development and testing only. Only read and write files. +! 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 (aking to clang) +! 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,13 +1,35 @@ -! RUN: %flang-new -help 2>&1 | FileCheck %s -! RUN: %flang-new -fc1 -help 2>&1 | FileCheck %s + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! RUN: %flang-new -help 2>&1 | FileCheck %s --check-prefix=CHECK-FLANG ! RUN: not %flang-new -helps 2>&1 | FileCheck %s --check-prefix=ERROR +!---------------------------------------- +! FLANG FRONTEND DRIVER (flang-new -fc1) +!---------------------------------------- +! RUN: %flang-new -fc1 -help 2>&1 | FileCheck %s --check-prefix=CHECK-FLANG-FC1 + + ! REQUIRES: new-flang-driver -! CHECK:USAGE: flang-new -! CHECK-EMPTY: -! CHECK-NEXT:OPTIONS: -! CHECK-NEXT: -help Display available options -! CHECK-NEXT: --version Print version information +!---------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG DRIVER (flang-new) +!---------------------------------------------------- +! CHECK-FLANG: USAGE: flang-new +! CHECK-FLANG-EMPTY: +! CHECK-FLANG-NEXT:OPTIONS: +! CHECK-FLANG-NEXT: -help Display available options +! CHECK-FLANG-NEXT: --version Print version information ! ERROR: error: unknown argument '-helps'; did you mean '-help' + +!------------------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG FRONTEND DRIVER (flang-new -fc1) +!------------------------------------------------------------- +! CHECK-FLANG-FC1: USAGE: flang-new +! CHECK-FLANG-FC1-EMPTY: +! CHECK-FLANG-FC1-NEXT:OPTIONS: +! CHECK-FLANG-FC1-NEXT: -help Display available options +! CHECK-FLANG-FC1-NEXT: -o Write output to +! CHECK-FLANG-FC1-NEXT: --version Print version information 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,4 @@ ! RUN: not %flang-new %s 2>&1 | FileCheck %s --check-prefix=ERROR-IMPLICIT -! RUN: not %flang-new -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR-EXPLICIT ! RUN: not %flang-new -fc1 -emit-obj %s 2>&1 | FileCheck %s --check-prefix=ERROR-FC1 ! REQUIRES: new-flang-driver @@ -10,8 +9,5 @@ ! ERROR-IMPLICIT: error: unknown argument: '-triple' ! ERROR-IMPLICIT: error: unknown argument: '-emit-obj' -! ERROR-IMPLICIT: error: unknown argument: '-o' - -! ERROR-EXPLICIT: error: unknown argument: '-o' ! ERROR-FC1: error: unknown argument: '-emit-obj' diff --git a/flang/test/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 @@ -52,5 +52,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 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 @@ -7,19 +7,54 @@ //===----------------------------------------------------------------------===// #include "flang/Frontend/CompilerInstance.h" -#include "gtest/gtest.h" -#include "flang/Frontend/CompilerInvocation.h" #include "clang/Basic/DiagnosticOptions.h" -#include "clang/Driver/Options.h" #include "clang/Frontend/TextDiagnosticPrinter.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" -#include using namespace llvm; using namespace Fortran::frontend; +#include namespace { +TEST(CompilerInstance, SanityCheckForInputFile) { + // 1. Prepare the input to be stores in a buffer + // Create the file to be used by AllSources + // Flang function 'FortranAllSources.Open' needs a physical file + // and the full path to work with + const char *inputSource = "InputSourceFile"; + std::string inputFilename = "buffer-file-test.f"; + std::error_code ec; + std::unique_ptr os{ + new llvm::raw_fd_ostream(inputFilename, ec, llvm::sys::fs::OF_None)}; + if (ec) + llvm::errs() << "Fail to create the file need by the test"; + *(os) << inputSource; + os.reset(); + std::string getFileFullPath = std::filesystem::current_path().c_str(); + getFileFullPath = getFileFullPath + "/" + inputFilename; + + // 2. Setting up the file/source manager for Compiler Instance + std::string buf; + llvm::raw_string_ostream error_stream{buf}; + CompilerInstance compInst; + Fortran::parser::AllSources &allSources{compInst.GetAllSources()}; + const Fortran::parser::SourceFile *sf; + sf = allSources.Open(getFileFullPath, error_stream); + llvm::ArrayRef fileContent = sf->content(); + + // 3. Verifies the content of the buffer in SourceFile + // Check that the compiler instance was able to write + EXPECT_FALSE(fileContent.size() == 0); + EXPECT_TRUE( + llvm::StringRef(fileContent.data()).startswith("InputSourceFile")); + + // 4. Delete file + llvm::sys::fs::remove(inputFilename); +} + 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,65 @@ +//===- 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 "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" +#include "gtest/gtest.h" + +using namespace Fortran::frontend; +#include + +namespace { + +TEST(FrontendOutputTests, TestInputOutputStreamOwned) { + // 1. Prepare the input file to be used by IO + // Create the file to be used by AllSources + // Flang function 'FortranAllSources.Open' needs a physical file + // and the full path to work with + std::string inputFilename = "io-file-test.f"; + std::error_code ec; + std::unique_ptr os{ + new llvm::raw_fd_ostream(inputFilename, ec, llvm::sys::fs::OF_None)}; + if (ec) + llvm::errs() << "Fail to create the file need by the test"; + *(os) << "End Program arithmetic"; + os.reset(); + std::string getFileFullPath = std::filesystem::current_path().c_str(); + getFileFullPath = getFileFullPath + "/" + inputFilename; + + // 2. Prepare the compiler (Invocation + Instance) + 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=*/getFileFullPath, 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. Remove files + compInst.ClearOutputFiles(/*EraseFiles=*/false); +} +} // namespace \ No newline at end of file diff --git a/llvm/lib/Option/OptTable.cpp b/llvm/lib/Option/OptTable.cpp --- a/llvm/lib/Option/OptTable.cpp +++ b/llvm/lib/Option/OptTable.cpp @@ -639,6 +639,7 @@ continue; unsigned Flags = getInfo(Id).Flags; + if (FlagsToInclude && !(Flags & FlagsToInclude)) continue; if (Flags & FlagsToExclude)