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 @@ -387,7 +387,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_; @@ -76,6 +78,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" @@ -34,12 +35,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 @@ -17,6 +17,10 @@ void ExecuteAction() override; }; +class PrintPreprocessedAction : 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 @@ -23,6 +23,8 @@ /// -test-io mode InputOutputTest, + /// -E mode. + PrintPreprocessedInput, /// TODO: ADD flags as the Actions are implemented // RunPreprocessor, ParserSyntaxOnly, EmitLLVM, EmitLLVMOnly, // EmitCodeGenOnly, EmitAssembly, (...) @@ -32,6 +34,8 @@ 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 @@ -9,7 +9,9 @@ clangBasic LINK_LIBS + FortranCommon FortranParser + FortranSemantics 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,6 +8,7 @@ #include "flang/Frontend/CompilerInstance.h" #include "flang/Frontend/CompilerInvocation.h" +#include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "clang/Frontend/TextDiagnosticPrinter.h" #include "llvm/Support/Errc.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?"); @@ -113,6 +116,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 @@ -47,8 +47,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" @@ -46,6 +45,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(); } @@ -59,4 +71,4 @@ SetCompilerInstance(nullptr); SetCurrentInput(FrontendInputFile()); -} \ No newline at end of file +} 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 @@ -19,6 +19,7 @@ ! CHECK:USAGE: flang-new ! CHECK-EMPTY: ! CHECK-NEXT:OPTIONS: +! CHECK-NEXT: -E Only run the preprocessor ! CHECK-NEXT: -help Display available options ! CHECK-NEXT: -test-io Use for Flang development and testing only. Only read and write files. 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 @@ -19,6 +19,7 @@ ! 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 @@ -30,6 +31,7 @@ ! CHECK-FLANG-FC1: USAGE: flang-new ! CHECK-FLANG-FC1-EMPTY: ! CHECK-FLANG-FC1-NEXT:OPTIONS: +! CHECK-FLANG-FC1-NEXT: -E Only run the preprocessor ! 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/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,6 +1,7 @@ add_flang_unittest(FlangFrontendTests CompilerInstanceTest.cpp InputOutputTest.cpp + PrintPreprocessedTest.cpp ) target_link_libraries(FlangFrontendTests 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,69 @@ +//===- 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/Frontend/CompilerInstance.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" + +#include + +using namespace Fortran::frontend; + +namespace { + +TEST(PrintPreprocessedTests, TestPreprocessedOutputStreamOwned) { + + // 1. Prepare the input source code + // Flang function 'SourceFile *Prescan' needs a physical file + // and the full path to work with + std::string inputFilename = "preprocessing-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) << "! preprocessing-file-test.F:\n #ifdef NEW \n Program A \n#else " + "\nProgram B \n #endif"; + os.reset(); + std::string getFileFullPath = std::filesystem::current_path().c_str(); + getFileFullPath = getFileFullPath + "/" + inputFilename; + + // 2. Set-up the output stream. Initialize buffer to be used to receive output + // file content + llvm::SmallVector outputFileBuffer; + std::unique_ptr outputFileStream( + new llvm::raw_svector_ostream(outputFileBuffer)); + + // 3. Prepare the compiler invocation with the preprocessing action + auto invocation = std::make_shared(); + invocation->GetFrontendOpts().programAction_ = PrintPreprocessedInput; + + // 4. Configure CompilerInstance and set the input file + CompilerInstance compInst; + compInst.CreateDiagnostics(); + compInst.SetOutputStream(std::move(outputFileStream)); + compInst.SetInvocation(std::move(invocation)); + compInst.GetFrontendOpts().inputs_.push_back( + FrontendInputFile(getFileFullPath, Language::Fortran90)); + + // 5. Run the earlier defined FrontendAction + bool success = ExecuteCompilerInvocation(&compInst); + + EXPECT_TRUE(success); + EXPECT_TRUE(!outputFileBuffer.empty()); + EXPECT_TRUE(llvm::StringRef(outputFileBuffer.data()).startswith("program b")); + + // 6. Delete output files in flight and the test file + llvm::sys::fs::remove(inputFilename); + compInst.ClearOutputFiles(/*EraseFiles=*/true); +} +} // namespace