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 @@ -390,7 +390,7 @@ def D : JoinedOrSeparate<["-"], "D">, Group, Flags<[CC1Option]>, MetaVarName<"=">, HelpText<"Define to (or 1 if omitted)">; -def E : Flag<["-"], "E">, Flags<[DriverOption,CC1Option]>, Group, +def E : Flag<["-"], "E">, Flags<[DriverOption,CC1Option, FlangOption, FC1Option]>, Group, HelpText<"Only run the preprocessor">; def F : JoinedOrSeparate<["-"], "F">, Flags<[RenderJoined,CC1Option]>, HelpText<"Add directory to framework include search path">; 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 @@ -10,12 +10,10 @@ #include "flang/Frontend/CompilerInvocation.h" #include "flang/Frontend/FrontendAction.h" +#include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "llvm/Support/raw_ostream.h" -#include -#include - namespace Fortran::frontend { class CompilerInstance { @@ -26,6 +24,10 @@ /// Flang file manager. std::shared_ptr allSources_; + std::shared_ptr allCookedSources_; + + std::shared_ptr parsing_; + /// The diagnostics engine instance. llvm::IntrusiveRefCntPtr diagnostics_; @@ -75,6 +77,13 @@ bool HasAllSources() const { return allSources_ != nullptr; } + /// } + /// @name Parser Operations + /// { + + /// Return parsing to be used by Actions. + Fortran::parser::Parsing &GetParsing() const { return *parsing_; } + /// } /// @name High-Level Operations /// { 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 @@ -9,6 +9,7 @@ #define LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H #include "flang/Frontend/FrontendOptions.h" +#include "flang/Parser/parsing.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "llvm/Option/ArgList.h" @@ -43,12 +44,20 @@ /// Options controlling the frontend itself. FrontendOptions frontendOpts_; + // Maps clang::driver options to frontend options use by + // Fortran::parser::Prescan + // TODO: map the option needed by the frontend + Fortran::parser::Options options_; + public: CompilerInvocation() = default; FrontendOptions &GetFrontendOpts() { return frontendOpts_; } const FrontendOptions &GetFrontendOpts() const { return frontendOpts_; } + Fortran::parser::Options &GetFortranOpts() { return options_; } + const Fortran::parser::Options &GetFortranOpts() const { return options_; } + /// Create a compiler invocation from a list of input options. /// \returns true on success. /// \returns false if an error was encountered while parsing the arguments diff --git a/flang/include/flang/Frontend/FrontendActions.h b/flang/include/flang/Frontend/FrontendActions.h --- a/flang/include/flang/Frontend/FrontendActions.h +++ b/flang/include/flang/Frontend/FrontendActions.h @@ -21,6 +21,10 @@ void ExecuteAction() override; }; +class PrintPreprocessedAction : 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 @@ -22,15 +22,18 @@ /// -test-io mode InputOutputTest, - // TODO: ADD flags as the Actions are implemented, e.g. - // RunPreprocessor, ParserSyntaxOnly, EmitLLVM, EmitLLVMOnly, - // EmitCodeGenOnly, EmitAssembly, (...) + /// -E mode. + PrintPreprocessedInput, + /// TODO: RunPreprocessor, ParserSyntaxOnly, EmitLLVM, EmitLLVMOnly, + /// EmitCodeGenOnly, EmitAssembly, (...) }; inline const char *GetActionKindName(const ActionKind ak) { switch (ak) { case InputOutputTest: return "InputOutputTest"; + case PrintPreprocessedInput: + return "PrintPreprocessedInput"; default: return ""; // TODO: 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 @@ -12,7 +12,9 @@ clangBasic LINK_LIBS + FortranCommon FortranParser + FortranSemantics 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,6 +9,7 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/CompilerInvocation.h" #include "flang/Frontend/TextDiagnosticPrinter.h" +#include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" @@ -20,7 +21,9 @@ CompilerInstance::CompilerInstance() : invocation_(new CompilerInvocation()), - allSources_(new Fortran::parser::AllSources()) {} + allSources_(new Fortran::parser::AllSources()), + allCookedSources_(new Fortran::parser::AllCookedSources(*allSources_)), + parsing_(new Fortran::parser::Parsing(*allCookedSources_)) {} CompilerInstance::~CompilerInstance() { assert(outputFiles_.empty() && "Still output files in flight?"); @@ -110,6 +113,15 @@ bool CompilerInstance::ExecuteAction(FrontendAction &act) { + // TODO:Remove once ExecuteCompilerInvocation maps driver::option to + // Fortran::parser::option + // Set defaults for Parser, as it use as flang options + // Default consistent with the temporary driver in f18/f18.cpp + std::vector searchDirectories{"."s}; + GetInvocation().GetFortranOpts().searchDirectories = searchDirectories; + GetInvocation().GetFortranOpts().isFixedForm = false; + GetAllSources().set_encoding(Fortran::parser::Encoding::UTF_8); + // Connect Input to a CompileInstance for (const FrontendInputFile &fif : GetFrontendOpts().inputs_) { if (act.BeginSourceFile(*this, fif)) { 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 @@ -90,8 +90,11 @@ case clang::driver::options::OPT_test_io: opts.programAction_ = InputOutputTest; break; + case clang::driver::options::OPT_E: + opts.programAction_ = PrintPreprocessedInput; + break; + // TODO: - // case clang::driver::options::OPT_E: // case clang::driver::options::OPT_emit_obj: // case calng::driver::options::OPT_emit_llvm: // case clang::driver::options::OPT_emit_llvm_only: diff --git a/flang/lib/Frontend/FrontendAction.cpp b/flang/lib/Frontend/FrontendAction.cpp --- a/flang/lib/Frontend/FrontendAction.cpp +++ b/flang/lib/Frontend/FrontendAction.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// - #include "flang/Frontend/FrontendAction.h" #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/FrontendActions.h" @@ -45,6 +44,19 @@ } llvm::Error FrontendAction::Execute() { + + // Get file's name from FrontendInputFile current + std::string path{GetCurrentFileOrBufferName()}; + + // Option is used inside Parser::Prescan + // Frontend driver use Fortran::parser::Option to have the 2 drivers working + Fortran::parser::Options options = + GetCompilerInstance().GetInvocation().GetFortranOpts(); + + // Read files, scan and run preprocessor + // Needed by all next fases of the frontend + GetCompilerInstance().GetParsing().Prescan(path, options); + ExecuteAction(); return llvm::Error::success(); } diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -5,12 +5,12 @@ // 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/parsing.h" +#include "flang/Parser/provenance.h" #include "flang/Parser/source.h" -#include "clang/Serialization/PCHContainerOperations.h" using namespace Fortran::frontend; @@ -43,3 +43,29 @@ ci.WriteOutputStream(fileContent.data()); } } + +void PrintPreprocessedAction::ExecuteAction() { + + std::string buf; + llvm::raw_string_ostream out{buf}; + + // Fortran::parser writes on out the parseState content + CompilerInstance &ci = GetCompilerInstance(); + ci.GetParsing().DumpCookedChars(out); + + // Writes the preprocessed content into the output + if (!ci.IsOutputStreamNull()) { + // Send the output to the pre-defined output buffer. + ci.WriteOutputStream(out.str()); + } else { + std::unique_ptr os; + os = ci.CreateDefaultOutputFile( + /*Binary=*/true, /*InFile=*/GetCurrentFileOrBufferName()); + if (!os) { + llvm::errs() << "Unable to create the output file\n"; + return; + } + + (*os) << out.str(); + } +} 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 @@ -29,6 +29,9 @@ case InputOutputTest: return std::make_unique(); break; + case PrintPreprocessedInput: + return std::make_unique(); + break; default: break; // TODO: diff --git a/flang/test/Flang-Driver/driver-help-hidden.f90 b/flang/test/Flang-Driver/driver-help-hidden.f90 --- a/flang/test/Flang-Driver/driver-help-hidden.f90 +++ b/flang/test/Flang-Driver/driver-help-hidden.f90 @@ -18,6 +18,7 @@ ! CHECK:USAGE: flang-new ! CHECK-EMPTY: ! CHECK-NEXT:OPTIONS: +! CHECK-NEXT: -E Only run the preprocessor ! CHECK-NEXT: -fcolor-diagnostics Enable colors in diagnostics ! CHECK-NEXT: -fno-color-diagnostics Disable colors in diagnostics ! CHECK-NEXT: -help Display available options 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 @@ -24,16 +24,27 @@ ! HELP-NEXT: -o Write output to ! HELP-NEXT: --version Print version information -!---------------------------------- -! EXPECTED OUTPUT (flang-new -fc1) -!---------------------------------- +!------------------------------------------------------------- +! EXPECTED OUTPUT FOR FLANG FRONTEND DRIVER (flang-new -fc1) +!------------------------------------------------------------- ! HELP-FC1:USAGE: flang-new ! HELP-FC1-EMPTY: ! HELP-FC1-NEXT:OPTIONS: +! HELP-FC1-NEXT: -E Only run the preprocessor ! HELP-FC1-NEXT: -help Display available options ! HELP-FC1-NEXT: -o Write output to ! HELP-FC1-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: -E Only run the preprocessor +! CHECK-FLANG-NEXT: -help Display available options +! CHECK-FLANG-NEXT: --version Print version information + !--------------- ! EXPECTED ERROR !--------------- diff --git a/flang/test/Frontend/Inputs/hello-world.c b/flang/test/Frontend/Inputs/hello-world.c new file mode 100644 --- /dev/null +++ b/flang/test/Frontend/Inputs/hello-world.c @@ -0,0 +1,5 @@ +a #include int main() { + // printf() displays the string inside quotation + printf("Hello, World!"); + return 0; +} \ No newline at end of file diff --git a/flang/test/Frontend/print-preprocess-C-file.f90 b/flang/test/Frontend/print-preprocess-C-file.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Frontend/print-preprocess-C-file.f90 @@ -0,0 +1,16 @@ +! Test preprocessing for C files using Flang driver + +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! TEST 1: Print to stdout (implicit) +! RUN: not %flang-new -E %S/Inputs/hello-world.c 2>&1 | FileCheck %s +! RUN: not %flang-new -E %S/Inputs/hello-world.c -o - 2>&1 | FileCheck %s +! RUN: not %flang-new -E %S/Inputs/hello-world.c -o test.F90 2>&1 | FileCheck %s + +!----------------------- +! EXPECTED OUTPUT +!----------------------- +! CHECK: error: unknown integrated tool '-cc1'. Valid tools include '-fc1'. \ No newline at end of file diff --git a/flang/test/Frontend/print-preprocessed-file.f90 b/flang/test/Frontend/print-preprocessed-file.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Frontend/print-preprocessed-file.f90 @@ -0,0 +1,39 @@ +! Test printpreprocessed action + +! REQUIRES: new-flang-driver + +!-------------------------- +! FLANG DRIVER (flang-new) +!-------------------------- +! RUN: %flang-new -E %s 2>&1 | FileCheck %s +! RUN: %flang-new -E -o - %s 2>&1 | FileCheck %s +! RUN: %flang-new -E -o %t %s 2>&1 && FileCheck %s --input-file=%t + +!----------------------------------------- +! FRONTEND FLANG DRIVER (flang-new -fc1) +!----------------------------------------- +! RUN: %flang-new -fc1 -E %s 2>&1 | FileCheck %s +! RUN: %flang-new -fc1 -E -o - %s 2>&1 | FileCheck %s +! RUN: %flang-new -fc1 -E -o %t %s 2>&1 && FileCheck %s --input-file=%t + + +!----------------------- +! EXPECTED OUTPUT +!----------------------- +! flang-new -E %s +! CHECK:program a +! CHECK-NOT:program b +! CHECK-NEXT:x = 1 +! CHECK-NEXT:write(*,*) x +! CHECK-NEXT:end + +! Preprocessed-file.F: +#define NEW +#ifdef NEW + program A +#else + program B +#endif + x = 1 + write(*,*) x + end \ No newline at end of file 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,11 +1,13 @@ add_flang_unittest(FlangFrontendTests CompilerInstanceTest.cpp InputOutputTest.cpp + PrintPreprocessedTest.cpp ) target_link_libraries(FlangFrontendTests PRIVATE clangBasic clangFrontend + FortranParser flangFrontend flangFrontendTool) diff --git a/flang/unittests/Frontend/PrintPreprocessedTest.cpp b/flang/unittests/Frontend/PrintPreprocessedTest.cpp new file mode 100644 --- /dev/null +++ b/flang/unittests/Frontend/PrintPreprocessedTest.cpp @@ -0,0 +1,79 @@ +//===- unittests/Frontend/PrintPreprocessedTest.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/unittests/Frontend/PrintPreprocessedTest.cpp" +#include "gtest/gtest.h" +#include "flang/Frontend/CompilerInstance.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, PrintPreprocessedInput) { + std::string inputFile = "test-file.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() << "Fail to create the file need by the test"; + + // Populate the input file with the pre-defined input and flush it. + *(os) << "! test-file.F:\n" + << "#ifdef NEW\n" + << " Program A \n" + << "#else\n" + << " Program B\n" + << "#endif"; + 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_ = PrintPreprocessedInput; + + compInst.SetInvocation(std::move(invocation)); + compInst.GetFrontendOpts().inputs_.push_back( + FrontendInputFile(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); + + // 5. Validate the expected output + EXPECT_TRUE(success); + EXPECT_TRUE(!outputFileBuffer.empty()); + EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()).equals("program b")); + + // 6. Clear the input and the output files. Since we used an output buffer, + // there are no physical output files to delete. + llvm::sys::fs::remove(inputFile); + compInst.ClearOutputFiles(/*EraseFiles=*/true); +} +} // namespace