diff --git a/flang/tools/f18/CMakeLists.txt b/flang/tools/f18/CMakeLists.txt index e9aad661af3c..c04cb47130cb 100644 --- a/flang/tools/f18/CMakeLists.txt +++ b/flang/tools/f18/CMakeLists.txt @@ -29,3 +29,12 @@ target_link_libraries(f18 ${FORTRAN_FIR_LIB} ${LLVM_COMMON_LIBS} ) + +add_executable(f18-parse-demo + f18-parse-demo.cc +) + +target_link_libraries(f18-parse-demo + FortranParser + FortranSemantics +) diff --git a/flang/tools/f18/f18-parse-demo.cc b/flang/tools/f18/f18-parse-demo.cc new file mode 100644 index 000000000000..b39e3160aaec --- /dev/null +++ b/flang/tools/f18/f18-parse-demo.cc @@ -0,0 +1,476 @@ +// Copyright (c) 2018-2019, NVIDIA CORPORATION. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// F18 parsing demonstration. +// f18-parse-demo [ -E | -fdump-parse-tree | -funparse-only ] +// foo.{f,F,f77,F77,f90,F90,&c.} +// +// By default, runs the supplied source files through the F18 preprocessing and +// parsing phases, reconstitutes a Fortran program from the parse tree, and +// passes that Fortran program to a Fortran compiler identified by the $F18_FC +// environment variable (defaulting to gfortran). The Fortran preprocessor is +// always run, whatever the case of the source file extension. Unrecognized +// options are passed through to the underlying Fortran compiler. +// +// This program is actually a stripped-down variant of f18.cc, a temporary +// scaffolding compiler driver that can test some semantic passes of the +// F18 compiler under development. + +#include "../../lib/common/default-kinds.h" +#include "../../lib/parser/characters.h" +#include "../../lib/parser/features.h" +#include "../../lib/parser/message.h" +#include "../../lib/parser/parse-tree-visitor.h" +#include "../../lib/parser/parse-tree.h" +#include "../../lib/parser/parsing.h" +#include "../../lib/parser/provenance.h" +#include "../../lib/parser/unparse.h" +#include "../../lib/semantics/dump-parse-tree.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::list argList(int argc, char *const argv[]) { + std::list result; + for (int j = 0; j < argc; ++j) { + result.emplace_back(argv[j]); + } + return result; +} + +std::vector filesToDelete; + +void CleanUpAtExit() { + for (const auto &path : filesToDelete) { + if (!path.empty()) { + unlink(path.data()); + } + } +} + +double CPUseconds() { + struct timespec tspec; + clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tspec); + return tspec.tv_nsec * 1.0e-9 + tspec.tv_sec; +} + +struct DriverOptions { + DriverOptions() {} + bool verbose{false}; // -v + bool compileOnly{false}; // -c + std::string outputPath; // -o path + std::vector searchDirectories{"."s}; // -I dir + std::string moduleDirectory{"."s}; // -module dir + bool forcedForm{false}; // -Mfixed or -Mfree appeared + bool warnOnNonstandardUsage{false}; // -Mstandard + bool warningsAreErrors{false}; // -Werror + Fortran::parser::Encoding encoding{Fortran::parser::Encoding::UTF8}; + bool parseOnly{false}; + bool dumpProvenance{false}; + bool dumpCookedChars{false}; + bool dumpUnparse{false}; + bool dumpUnparseWithSymbols{false}; + bool dumpParseTree{false}; + bool timeParse{false}; + std::vector fcArgs; + const char *prefix{nullptr}; +}; + +bool ParentProcess() { + if (fork() == 0) { + return false; // in child process + } + int childStat{0}; + wait(&childStat); + if (!WIFEXITED(childStat) || WEXITSTATUS(childStat) != 0) { + exit(EXIT_FAILURE); + } + return true; +} + +void Exec(std::vector &argv, bool verbose = false) { + if (verbose) { + for (size_t j{0}; j < argv.size(); ++j) { + std::cerr << (j > 0 ? " " : "") << argv[j]; + } + std::cerr << '\n'; + } + argv.push_back(nullptr); + execvp(argv[0], &argv[0]); + std::cerr << "execvp(" << argv[0] << ") failed: " << std::strerror(errno) + << '\n'; + exit(EXIT_FAILURE); +} + +void RunOtherCompiler(DriverOptions &driver, char *source, char *relo) { + std::vector argv; + for (size_t j{0}; j < driver.fcArgs.size(); ++j) { + argv.push_back(driver.fcArgs[j].data()); + } + char dashC[3] = "-c", dashO[3] = "-o"; + argv.push_back(dashC); + argv.push_back(dashO); + argv.push_back(relo); + argv.push_back(source); + Exec(argv, driver.verbose); +} + +std::string RelocatableName(const DriverOptions &driver, std::string path) { + if (driver.compileOnly && !driver.outputPath.empty()) { + return driver.outputPath; + } + std::string base{path}; + auto slash{base.rfind("/")}; + if (slash != std::string::npos) { + base = base.substr(slash + 1); + } + std::string relo{base}; + auto dot{base.rfind(".")}; + if (dot != std::string::npos) { + relo = base.substr(0, dot); + } + relo += ".o"; + return relo; +} + +int exitStatus{EXIT_SUCCESS}; + +std::string CompileFortran( + std::string path, Fortran::parser::Options options, DriverOptions &driver) { + if (!driver.forcedForm) { + auto dot{path.rfind(".")}; + if (dot != std::string::npos) { + std::string suffix{path.substr(dot + 1)}; + options.isFixedForm = suffix == "f" || suffix == "F" || suffix == "ff"; + } + } + options.searchDirectories = driver.searchDirectories; + Fortran::parser::Parsing parsing; + + auto start{CPUseconds()}; + parsing.Prescan(path, options); + if (!parsing.messages().empty() && + (driver.warningsAreErrors || parsing.messages().AnyFatalError())) { + std::cerr << driver.prefix << "could not scan " << path << '\n'; + parsing.messages().Emit(std::cerr, parsing.cooked()); + exitStatus = EXIT_FAILURE; + return {}; + } + if (driver.dumpProvenance) { + parsing.DumpProvenance(std::cout); + return {}; + } + if (driver.dumpCookedChars) { + parsing.DumpCookedChars(std::cout); + return {}; + } + parsing.Parse(&std::cout); + auto stop{CPUseconds()}; + if (driver.timeParse) { + std::cout << "parse time for " << path << ": " << (stop - start) + << " CPU seconds\n"; + } + + parsing.ClearLog(); + parsing.messages().Emit(std::cerr, parsing.cooked()); + if (!parsing.consumedWholeFile()) { + parsing.EmitMessage( + std::cerr, parsing.finalRestingPlace(), "parser FAIL (final position)"); + exitStatus = EXIT_FAILURE; + return {}; + } + if ((!parsing.messages().empty() && + (driver.warningsAreErrors || parsing.messages().AnyFatalError())) || + !parsing.parseTree().has_value()) { + std::cerr << driver.prefix << "could not parse " << path << '\n'; + exitStatus = EXIT_FAILURE; + return {}; + } + auto &parseTree{*parsing.parseTree()}; + if (driver.dumpParseTree) { + Fortran::semantics::DumpTree(std::cout, parseTree); + return {}; + } + if (driver.dumpUnparse) { + Unparse(std::cout, parseTree, driver.encoding, true /*capitalize*/, + options.features.IsEnabled( + Fortran::parser::LanguageFeature::BackslashEscapes)); + return {}; + } + if (driver.parseOnly) { + return {}; + } + + std::string relo{RelocatableName(driver, path)}; + + char tmpSourcePath[32]; + std::snprintf(tmpSourcePath, sizeof tmpSourcePath, "/tmp/f18-%lx.f90", + static_cast(getpid())); + { + std::ofstream tmpSource; + tmpSource.open(tmpSourcePath); + Unparse(tmpSource, parseTree, driver.encoding, true /*capitalize*/, + options.features.IsEnabled( + Fortran::parser::LanguageFeature::BackslashEscapes)); + } + + if (ParentProcess()) { + filesToDelete.push_back(tmpSourcePath); + if (!driver.compileOnly && driver.outputPath.empty()) { + filesToDelete.push_back(relo); + } + return relo; + } + RunOtherCompiler(driver, tmpSourcePath, relo.data()); + return {}; +} + +std::string CompileOtherLanguage(std::string path, DriverOptions &driver) { + std::string relo{RelocatableName(driver, path)}; + if (ParentProcess()) { + if (!driver.compileOnly && driver.outputPath.empty()) { + filesToDelete.push_back(relo); + } + return relo; + } + RunOtherCompiler(driver, path.data(), relo.data()); + return {}; +} + +void Link(std::vector &relocatables, DriverOptions &driver) { + if (!ParentProcess()) { + std::vector argv; + for (size_t j{0}; j < driver.fcArgs.size(); ++j) { + argv.push_back(driver.fcArgs[j].data()); + } + for (auto &relo : relocatables) { + argv.push_back(relo.data()); + } + if (!driver.outputPath.empty()) { + char dashO[3] = "-o"; + argv.push_back(dashO); + argv.push_back(driver.outputPath.data()); + } + Exec(argv, driver.verbose); + } +} + +int main(int argc, char *const argv[]) { + + atexit(CleanUpAtExit); + + DriverOptions driver; + const char *fc{getenv("F18_FC")}; + driver.fcArgs.push_back(fc ? fc : "gfortran"); + + std::list args{argList(argc, argv)}; + std::string prefix{args.front()}; + args.pop_front(); + prefix += ": "; + driver.prefix = prefix.data(); + + Fortran::parser::Options options; + options.predefinitions.emplace_back("__F18", "1"); + options.predefinitions.emplace_back("__F18_MAJOR__", "1"); + options.predefinitions.emplace_back("__F18_MINOR__", "1"); + options.predefinitions.emplace_back("__F18_PATCHLEVEL__", "1"); + + options.features.Enable( + Fortran::parser::LanguageFeature::BackslashEscapes, true); + + Fortran::common::IntrinsicTypeDefaultKinds defaultKinds; + + std::vector fortranSources, otherSources, relocatables; + bool anyFiles{false}; + + while (!args.empty()) { + std::string arg{std::move(args.front())}; + args.pop_front(); + if (arg.empty()) { + } else if (arg.at(0) != '-') { + anyFiles = true; + auto dot{arg.rfind(".")}; + if (dot == std::string::npos) { + driver.fcArgs.push_back(arg); + } else { + std::string suffix{arg.substr(dot + 1)}; + if (suffix == "f" || suffix == "F" || suffix == "ff" || + suffix == "f90" || suffix == "F90" || suffix == "ff90" || + suffix == "f95" || suffix == "F95" || suffix == "ff95" || + suffix == "cuf" || suffix == "CUF" || suffix == "f18" || + suffix == "F18" || suffix == "ff18") { + fortranSources.push_back(arg); + } else if (suffix == "o" || suffix == "a") { + relocatables.push_back(arg); + } else { + otherSources.push_back(arg); + } + } + } else if (arg == "-") { + fortranSources.push_back("-"); + } else if (arg == "--") { + while (!args.empty()) { + fortranSources.emplace_back(std::move(args.front())); + args.pop_front(); + } + break; + } else if (arg == "-Mfixed") { + driver.forcedForm = true; + options.isFixedForm = true; + } else if (arg == "-Mfree") { + driver.forcedForm = true; + options.isFixedForm = false; + } else if (arg == "-Mextend") { + options.fixedFormColumns = 132; + } else if (arg == "-Mbackslash") { + options.features.Enable( + Fortran::parser::LanguageFeature::BackslashEscapes, false); + } else if (arg == "-Mnobackslash") { + options.features.Enable( + Fortran::parser::LanguageFeature::BackslashEscapes); + } else if (arg == "-Mstandard") { + driver.warnOnNonstandardUsage = true; + } else if (arg == "-fopenmp") { + options.features.Enable(Fortran::parser::LanguageFeature::OpenMP); + options.predefinitions.emplace_back("_OPENMP", "201511"); + } else if (arg == "-Werror") { + driver.warningsAreErrors = true; + } else if (arg == "-ed") { + options.features.Enable(Fortran::parser::LanguageFeature::OldDebugLines); + } else if (arg == "-E" || arg == "-fpreprocess-only") { + driver.dumpCookedChars = true; + } else if (arg == "-fbackslash") { + options.features.Enable( + Fortran::parser::LanguageFeature::BackslashEscapes); + } else if (arg == "-fno-backslash") { + options.features.Enable( + Fortran::parser::LanguageFeature::BackslashEscapes, false); + } else if (arg == "-fdump-provenance") { + driver.dumpProvenance = true; + } else if (arg == "-fdump-parse-tree") { + driver.dumpParseTree = true; + } else if (arg == "-funparse") { + driver.dumpUnparse = true; + } else if (arg == "-ftime-parse") { + driver.timeParse = true; + } else if (arg == "-fparse-only") { + driver.parseOnly = true; + } else if (arg == "-c") { + driver.compileOnly = true; + } else if (arg == "-o") { + driver.outputPath = args.front(); + args.pop_front(); + } else if (arg.substr(0, 2) == "-D") { + auto eq{arg.find('=')}; + if (eq == std::string::npos) { + options.predefinitions.emplace_back(arg.substr(2), "1"); + } else { + options.predefinitions.emplace_back( + arg.substr(2, eq - 2), arg.substr(eq + 1)); + } + } else if (arg.substr(0, 2) == "-U") { + options.predefinitions.emplace_back( + arg.substr(2), std::optional{}); + } else if (arg == "-r8" || arg == "-fdefault-real-8") { + defaultKinds.set_defaultRealKind(8); + } else if (arg == "-i8" || arg == "-fdefault-integer-8") { + defaultKinds.set_defaultIntegerKind(8); + } else if (arg == "-fno-large-arrays") { + defaultKinds.set_subscriptIntegerKind(4); + } else if (arg == "-help" || arg == "--help" || arg == "-?") { + std::cerr + << "f18-parse-demo options:\n" + << " -Mfixed | -Mfree force the source form\n" + << " -Mextend 132-column fixed form\n" + << " -f[no-]backslash enable[disable] \\escapes in literals\n" + << " -M[no]backslash disable[enable] \\escapes in literals\n" + << " -Mstandard enable conformance warnings\n" + << " -r8 | -fdefault-real-8 | -i8 | -fdefault-integer-8 " + "change default kinds of intrinsic types\n" + << " -Werror treat warnings as errors\n" + << " -ed enable fixed form D lines\n" + << " -E prescan & preprocess only\n" + << " -ftime-parse measure parsing time\n" + << " -fparse-only parse only, no output except messages\n" + << " -funparse parse & reformat only, no code " + "generation\n" + << " -fdump-provenance dump the provenance table (no code)\n" + << " -fdump-parse-tree dump the parse tree (no code)\n" + << " -v -c -o -I -D -U have their usual meanings\n" + << " -help print this again\n" + << "Other options are passed through to the $F18_FC compiler.\n"; + return exitStatus; + } else if (arg == "-V") { + std::cerr << "\nf18-parse-demo\n"; + return exitStatus; + } else { + driver.fcArgs.push_back(arg); + if (arg == "-v") { + driver.verbose = true; + } else if (arg == "-I") { + driver.fcArgs.push_back(args.front()); + driver.searchDirectories.push_back(args.front()); + args.pop_front(); + } else if (arg.substr(0, 2) == "-I") { + driver.searchDirectories.push_back(arg.substr(2)); + } else if (arg == "-module") { + driver.moduleDirectory = args.front(); + driver.fcArgs.push_back(driver.moduleDirectory); + args.pop_front(); + } + } + } + driver.encoding = options.encoding; + + if (driver.warnOnNonstandardUsage) { + options.features.WarnOnAllNonstandard(); + } + if (!options.features.IsEnabled( + Fortran::parser::LanguageFeature::BackslashEscapes)) { + driver.fcArgs.push_back("-fno-backslash"); // PGI "-Mbackslash" + } + + if (!anyFiles) { + driver.dumpUnparse = true; + CompileFortran("-", options, driver); + return exitStatus; + } + for (const auto &path : fortranSources) { + std::string relo{CompileFortran(path, options, driver)}; + if (!driver.compileOnly && !relo.empty()) { + relocatables.push_back(relo); + } + } + for (const auto &path : otherSources) { + std::string relo{CompileOtherLanguage(path, driver)}; + if (!driver.compileOnly && !relo.empty()) { + relocatables.push_back(relo); + } + } + if (!relocatables.empty()) { + Link(relocatables, driver); + } + return exitStatus; +}