diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 7c41d38e00a49..2721c1b5d8dc5 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -4108,7 +4108,7 @@ defm ms_tls_guards : BoolFOption<"ms-tls-guards", "Do not emit code to perform on-demand initialization of thread-local variables">, PosFlag>; def ftime_report : Flag<["-"], "ftime-report">, Group, - Visibility<[ClangOption, CC1Option]>, + Visibility<[ClangOption, CC1Option, FlangOption, FC1Option]>, MarshallingInfoFlag>; def ftime_report_EQ: Joined<["-"], "ftime-report=">, Group, Visibility<[ClangOption, CC1Option]>, Values<"per-pass,per-pass-run">, diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp index 75b10e88371ae..a7d0cc99f27d2 100644 --- a/clang/lib/Driver/ToolChains/Flang.cpp +++ b/clang/lib/Driver/ToolChains/Flang.cpp @@ -150,10 +150,12 @@ void Flang::addCodegenOptions(const ArgList &Args, if (shouldLoopVersion(Args)) CmdArgs.push_back("-fversion-loops-for-stride"); - Args.addAllArgs(CmdArgs, {options::OPT_flang_experimental_hlfir, - options::OPT_flang_deprecated_no_hlfir, - options::OPT_fno_ppc_native_vec_elem_order, - options::OPT_fppc_native_vec_elem_order}); + Args.addAllArgs(CmdArgs, + {options::OPT_flang_experimental_hlfir, + options::OPT_flang_deprecated_no_hlfir, + options::OPT_fno_ppc_native_vec_elem_order, + options::OPT_fppc_native_vec_elem_order, + options::OPT_ftime_report, options::OPT_ftime_report_EQ}); } void Flang::addPicOptions(const ArgList &Args, ArgStringList &CmdArgs) const { diff --git a/flang/include/flang/Frontend/CompilerInstance.h b/flang/include/flang/Frontend/CompilerInstance.h index 4fcc59f7cf577..509c9f4b9e91a 100644 --- a/flang/include/flang/Frontend/CompilerInstance.h +++ b/flang/include/flang/Frontend/CompilerInstance.h @@ -20,6 +20,7 @@ #include "flang/Parser/provenance.h" #include "flang/Semantics/runtime-type-info.h" #include "flang/Semantics/semantics.h" +#include "flang/Support/StringOstream.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Target/TargetMachine.h" @@ -85,6 +86,27 @@ class CompilerInstance { /// facilitate this. It is optional and will normally be just a nullptr. std::unique_ptr outputStream; + /// @name Timing + /// Objects needed when timing is enabled. + /// @{ + /// The timing manager. + mlir::DefaultTimingManager timingMgr; + + /// The root of the timingScope. This will be reset in @ref executeAction if + /// timers have been enabled. + mlir::TimingScope timingScopeRoot; + + /// @name Timing stream + /// The output streams to capture the timing. Three different streams are + /// needed because the timing classes all work slightly differently. We create + /// these streams so we have control over when and how the timing is + /// displayed. Otherwise, the timing is only displayed when the corresponding + /// managers/timers go out of scope. + std::unique_ptr timingStreamMLIR; + std::unique_ptr timingStreamLLVM; + std::unique_ptr timingStreamCodeGen; + /// @} + public: explicit CompilerInstance(); @@ -254,6 +276,42 @@ class CompilerInstance { /// Produces the string which represents target feature std::string getTargetFeatures(); + /// { + /// @name Timing + /// @{ + bool isTimingEnabled() const { return timingMgr.isEnabled(); } + + mlir::DefaultTimingManager &getTimingManager() { return timingMgr; } + const mlir::DefaultTimingManager &getTimingManager() const { + return timingMgr; + } + + mlir::TimingScope &getTimingScopeRoot() { return timingScopeRoot; } + const mlir::TimingScope &getTimingScopeRoot() const { + return timingScopeRoot; + } + + /// Get the timing stream for the MLIR pass manager. + llvm::raw_ostream &getTimingStreamMLIR() { + assert(timingStreamMLIR && "Timing stream for MLIR was not set"); + return *timingStreamMLIR; + } + + /// Get the timing stream for the new LLVM pass manager. + llvm::raw_ostream &getTimingStreamLLVM() { + assert(timingStreamLLVM && "Timing stream for LLVM was not set"); + return *timingStreamLLVM; + } + + /// Get the timing stream fro the legacy LLVM pass manager. + /// NOTE: If the codegen is updated to use the new pass manager, this should + /// no longer be needed. + llvm::raw_ostream &getTimingStreamCodeGen() { + assert(timingStreamCodeGen && "Timing stream for codegen was not set"); + return *timingStreamCodeGen; + } + /// @} + private: /// Create a new output file /// diff --git a/flang/include/flang/Frontend/CompilerInvocation.h b/flang/include/flang/Frontend/CompilerInvocation.h index 50d908d083202..7d3f0bdf2e510 100644 --- a/flang/include/flang/Frontend/CompilerInvocation.h +++ b/flang/include/flang/Frontend/CompilerInvocation.h @@ -21,6 +21,7 @@ #include "flang/Lower/LoweringOptions.h" #include "flang/Parser/parsing.h" #include "flang/Semantics/semantics.h" +#include "mlir/Support/Timing.h" #include "clang/Basic/Diagnostic.h" #include "clang/Basic/DiagnosticOptions.h" #include "llvm/Option/ArgList.h" @@ -143,6 +144,10 @@ class CompilerInvocation : public CompilerInvocationBase { }, }; + /// Whether to time the invocation. Set when -ftime-report or -ftime-report= + /// is enabled. + bool enableTimers; + public: CompilerInvocation() = default; @@ -222,6 +227,8 @@ class CompilerInvocation : public CompilerInvocationBase { return defaultKinds; } + bool getEnableTimers() const { return enableTimers; } + /// 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/Support/StringOstream.h b/flang/include/flang/Support/StringOstream.h new file mode 100644 index 0000000000000..2e5c87eae058c --- /dev/null +++ b/flang/include/flang/Support/StringOstream.h @@ -0,0 +1,32 @@ +//===-- CompilerInstance.h - Flang Compiler Instance ------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ +// +//===----------------------------------------------------------------------===// + +#ifndef FORTRAN_SUPPORT_STRINGOSTREAM_H +#define FORTRAN_SUPPORT_STRINGOSTREAM_H + +#include + +namespace Fortran::support { + +/// Helper class to maintain both the an llvm::raw_string_ostream object and +/// its associated buffer. +class string_ostream : public llvm::raw_string_ostream { +private: + std::string buf; + +public: + string_ostream() : llvm::raw_string_ostream(buf) {} +}; + +} // namespace Fortran::support + +#endif // FORTRAN_SUPPORT_STRINGOSTREAM_H diff --git a/flang/include/flang/Support/Timing.h b/flang/include/flang/Support/Timing.h new file mode 100644 index 0000000000000..75ba2a8d85f39 --- /dev/null +++ b/flang/include/flang/Support/Timing.h @@ -0,0 +1,27 @@ +//===- Timing.h - Execution time measurement facilities ---------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Facilities to measure and provide statistics on execution time. +// +//===----------------------------------------------------------------------===// + +#ifndef FORTRAN_SUPPORT_TIMING_H +#define FORTRAN_SUPPORT_TIMING_H + +#include "mlir/Support/Timing.h" + +namespace Fortran::support { + +/// Create a strategy to render the captured times in plain text. This is +/// intended to be passed to a TimingManager. +std::unique_ptr createTimingFormatterText( + llvm::raw_ostream &os); + +} // namespace Fortran::support + +#endif // FORTRAN_SUPPORT_TIMING_H diff --git a/flang/lib/CMakeLists.txt b/flang/lib/CMakeLists.txt index f41d4df1f07e3..2182e845b6a79 100644 --- a/flang/lib/CMakeLists.txt +++ b/flang/lib/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(Decimal) add_subdirectory(Lower) add_subdirectory(Parser) add_subdirectory(Semantics) +add_subdirectory(Support) add_subdirectory(Frontend) add_subdirectory(FrontendTool) diff --git a/flang/lib/Frontend/CMakeLists.txt b/flang/lib/Frontend/CMakeLists.txt index e954800c3b88b..1b90fe826af6c 100644 --- a/flang/lib/Frontend/CMakeLists.txt +++ b/flang/lib/Frontend/CMakeLists.txt @@ -29,6 +29,7 @@ add_flang_library(flangFrontend FortranEvaluate FortranCommon FortranLower + FortranSupport FIRDialect FIRDialectSupport FIRSupport diff --git a/flang/lib/Frontend/CompilerInstance.cpp b/flang/lib/Frontend/CompilerInstance.cpp index 35c2ae3c73e69..298790bae6655 100644 --- a/flang/lib/Frontend/CompilerInstance.cpp +++ b/flang/lib/Frontend/CompilerInstance.cpp @@ -17,9 +17,12 @@ #include "flang/Parser/parsing.h" #include "flang/Parser/provenance.h" #include "flang/Semantics/semantics.h" +#include "flang/Support/Timing.h" +#include "mlir/Support/RawOstreamExtras.h" #include "clang/Basic/DiagnosticFrontend.h" #include "llvm/ADT/StringExtras.h" #include "llvm/MC/TargetRegistry.h" +#include "llvm/Pass.h" #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" @@ -147,7 +150,7 @@ void CompilerInstance::clearOutputFiles(bool eraseFiles) { } bool CompilerInstance::executeAction(FrontendAction &act) { - auto &invoc = this->getInvocation(); + CompilerInvocation &invoc = this->getInvocation(); llvm::Triple targetTriple{llvm::Triple(invoc.getTargetOpts().triple)}; if (targetTriple.getArch() == llvm::Triple::ArchType::x86_64) { @@ -167,6 +170,25 @@ bool CompilerInstance::executeAction(FrontendAction &act) { // Set options controlling lowering to FIR. invoc.setLoweringOptions(); + if (invoc.getEnableTimers()) { + llvm::TimePassesIsEnabled = true; + + timingStreamMLIR = std::make_unique(); + timingStreamLLVM = std::make_unique(); + timingStreamCodeGen = std::make_unique(); + + timingMgr.setEnabled(true); + timingMgr.setDisplayMode(mlir::DefaultTimingManager::DisplayMode::Tree); + timingMgr.setOutput( + Fortran::support::createTimingFormatterText(*timingStreamMLIR)); + + // Creating a new TimingScope will automatically start the timer. Since this + // is the top-level timer, this is ok because it will end up capturing the + // time for all the bookkeeping and other tasks that take place between + // parsing, lowering etc. for which finer-grained timers will be created. + timingScopeRoot = timingMgr.getRootScope(); + } + // Run the frontend action `act` for every input file. for (const FrontendInputFile &fif : getFrontendOpts().inputs) { if (act.beginSourceFile(*this, fif)) { @@ -176,6 +198,34 @@ bool CompilerInstance::executeAction(FrontendAction &act) { act.endSourceFile(); } } + + if (timingMgr.isEnabled()) { + timingScopeRoot.stop(); + + // Write the timings to the associated output stream and clear all timers. + // We need to provide another stream because the TimingManager will attempt + // to print in its destructor even if it has been cleared. By the time that + // destructor runs, the output streams will have been destroyed, so give it + // a null stream. + timingMgr.print(); + timingMgr.setOutput( + Fortran::support::createTimingFormatterText(mlir::thread_safe_nulls())); + + // This prints the timings in "reverse" order, starting from code + // generation, followed by LLVM-IR optimizations, then MLIR optimizations + // and transformations and the frontend. If any of the steps are disabled, + // for instance because code generation was not performed, the strings + // will be empty. + if (!timingStreamCodeGen->str().empty()) + llvm::errs() << timingStreamCodeGen->str() << "\n"; + + if (!timingStreamLLVM->str().empty()) + llvm::errs() << timingStreamLLVM->str() << "\n"; + + if (!timingStreamMLIR->str().empty()) + llvm::errs() << timingStreamMLIR->str() << "\n"; + } + return !getDiagnostics().getClient()->getNumErrors(); } diff --git a/flang/lib/Frontend/CompilerInvocation.cpp b/flang/lib/Frontend/CompilerInvocation.cpp index 340efb1c63a5e..5e7127313c133 100644 --- a/flang/lib/Frontend/CompilerInvocation.cpp +++ b/flang/lib/Frontend/CompilerInvocation.cpp @@ -1436,6 +1436,10 @@ bool CompilerInvocation::createFromArgs( } } + // Process the timing-related options. + if (args.hasArg(clang::driver::options::OPT_ftime_report)) + invoc.enableTimers = true; + invoc.setArgv0(argv0); return success; diff --git a/flang/lib/Frontend/FrontendActions.cpp b/flang/lib/Frontend/FrontendActions.cpp index 310cd650349c7..52a18d59c7cda 100644 --- a/flang/lib/Frontend/FrontendActions.cpp +++ b/flang/lib/Frontend/FrontendActions.cpp @@ -84,6 +84,15 @@ extern cl::opt PrintPipelinePasses; using namespace Fortran::frontend; +constexpr llvm::StringLiteral timingIdParse = "Parse"; +constexpr llvm::StringLiteral timingIdMLIRGen = "MLIR generation"; +constexpr llvm::StringLiteral timingIdMLIRPasses = + "MLIR translation/optimization"; +constexpr llvm::StringLiteral timingIdLLVMIRGen = "LLVM IR generation"; +constexpr llvm::StringLiteral timingIdLLVMIRPasses = "LLVM IR optimizations"; +constexpr llvm::StringLiteral timingIdBackend = + "Assembly/Object code generation"; + // Declare plugin extension function declarations. #define HANDLE_EXTENSION(Ext) \ llvm::PassPluginLibraryInfo get##Ext##PluginInfo(); @@ -227,6 +236,14 @@ static void addAMDGPUSpecificMLIRItems(mlir::ModuleOp mlirModule, bool CodeGenAction::beginSourceFileAction() { llvmCtx = std::make_unique(); CompilerInstance &ci = this->getInstance(); + mlir::DefaultTimingManager &timingMgr = ci.getTimingManager(); + mlir::TimingScope &timingScopeRoot = ci.getTimingScopeRoot(); + + // This will provide timing information even when the input is an LLVM IR or + // MLIR file. That is fine because those do have to be parsed, so the label + // is still accurate. + mlir::TimingScope timingScopeParse = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdParse, timingMgr)); // If the input is an LLVM file, just parse it and return. if (this->getCurrentInput().getKind().getLanguage() == Language::LLVM_IR) { @@ -288,6 +305,10 @@ bool CodeGenAction::beginSourceFileAction() { if (!res) return res; + timingScopeParse.stop(); + mlir::TimingScope timingScopeMLIRGen = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdMLIRGen, timingMgr)); + // Create a LoweringBridge const common::IntrinsicTypeDefaultKinds &defKinds = ci.getSemanticsContext().defaultKinds(); @@ -322,6 +343,7 @@ bool CodeGenAction::beginSourceFileAction() { // constants etc. addDependentLibs(*mlirModule, ci); addAMDGPUSpecificMLIRItems(*mlirModule, ci); + timingScopeMLIRGen.stop(); // run the default passes. mlir::PassManager pm((*mlirModule)->getName(), @@ -344,6 +366,7 @@ bool CodeGenAction::beginSourceFileAction() { pm.enableVerifier(/*verifyPasses=*/true); pm.addPass(std::make_unique()); + pm.enableTiming(timingScopeMLIRGen); if (mlir::failed(pm.run(*mlirModule))) { unsigned diagID = ci.getDiagnostics().getCustomDiagID( @@ -352,6 +375,7 @@ bool CodeGenAction::beginSourceFileAction() { ci.getDiagnostics().Report(diagID); return false; } + timingScopeMLIRGen.stop(); // Print initial full MLIR module, before lowering or transformations, if // -save-temps has been specified. @@ -704,8 +728,10 @@ void CodeGenAction::lowerHLFIRToFIR() { assert(mlirModule && "The MLIR module has not been generated yet."); CompilerInstance &ci = this->getInstance(); - auto opts = ci.getInvocation().getCodeGenOpts(); + const CodeGenOptions &opts = ci.getInvocation().getCodeGenOpts(); llvm::OptimizationLevel level = mapToLevel(opts); + mlir::DefaultTimingManager &timingMgr = ci.getTimingManager(); + mlir::TimingScope &timingScopeRoot = ci.getTimingScopeRoot(); fir::support::loadDialects(*mlirCtx); @@ -724,6 +750,9 @@ void CodeGenAction::lowerHLFIRToFIR() { level); (void)mlir::applyPassManagerCLOptions(pm); + mlir::TimingScope timingScopeMLIRPasses = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdMLIRPasses, timingMgr)); + pm.enableTiming(timingScopeMLIRPasses); if (!mlir::succeeded(pm.run(*mlirModule))) { unsigned diagID = ci.getDiagnostics().getCustomDiagID( clang::DiagnosticsEngine::Error, "Lowering to FIR failed"); @@ -808,9 +837,12 @@ void CodeGenAction::generateLLVMIR() { assert(mlirModule && "The MLIR module has not been generated yet."); CompilerInstance &ci = this->getInstance(); - auto opts = ci.getInvocation().getCodeGenOpts(); - auto mathOpts = ci.getInvocation().getLoweringOpts().getMathOptions(); + CompilerInvocation &invoc = ci.getInvocation(); + const CodeGenOptions &opts = invoc.getCodeGenOpts(); + const auto &mathOpts = invoc.getLoweringOpts().getMathOptions(); llvm::OptimizationLevel level = mapToLevel(opts); + mlir::DefaultTimingManager &timingMgr = ci.getTimingManager(); + mlir::TimingScope &timingScopeRoot = ci.getTimingScopeRoot(); fir::support::loadDialects(*mlirCtx); mlir::DialectRegistry registry; @@ -846,11 +878,15 @@ void CodeGenAction::generateLLVMIR() { (void)mlir::applyPassManagerCLOptions(pm); // run the pass manager + mlir::TimingScope timingScopeMLIRPasses = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdMLIRPasses, timingMgr)); + pm.enableTiming(timingScopeMLIRPasses); if (!mlir::succeeded(pm.run(*mlirModule))) { unsigned diagID = ci.getDiagnostics().getCustomDiagID( clang::DiagnosticsEngine::Error, "Lowering to LLVM IR failed"); ci.getDiagnostics().Report(diagID); } + timingScopeMLIRPasses.stop(); // Print final MLIR module, just before translation into LLVM IR, if // -save-temps has been specified. @@ -863,6 +899,8 @@ void CodeGenAction::generateLLVMIR() { } // Translate to LLVM IR + mlir::TimingScope timingScopeLLVMIRGen = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdLLVMIRGen, timingMgr)); std::optional moduleName = mlirModule->getName(); llvmModule = mlir::translateModuleToLLVMIR( *mlirModule, *llvmCtx, moduleName ? *moduleName : "FIRModule"); @@ -969,11 +1007,12 @@ static void generateMachineCodeOrAssemblyImpl(clang::DiagnosticsEngine &diags, } void CodeGenAction::runOptimizationPipeline(llvm::raw_pwrite_stream &os) { - auto opts = getInstance().getInvocation().getCodeGenOpts(); - auto &diags = getInstance().getDiagnostics(); + CompilerInstance &ci = getInstance(); + const CodeGenOptions &opts = ci.getInvocation().getCodeGenOpts(); + clang::DiagnosticsEngine &diags = ci.getDiagnostics(); llvm::OptimizationLevel level = mapToLevel(opts); - llvm::TargetMachine *targetMachine = &getInstance().getTargetMachine(); + llvm::TargetMachine *targetMachine = &ci.getTargetMachine(); // Create the analysis managers. llvm::LoopAnalysisManager lam; llvm::FunctionAnalysisManager fam; @@ -987,6 +1026,8 @@ void CodeGenAction::runOptimizationPipeline(llvm::raw_pwrite_stream &os) { llvm::StandardInstrumentations si(llvmModule->getContext(), opts.DebugPassManager); si.registerCallbacks(pic, &mam); + if (ci.isTimingEnabled()) + si.getTimePasses().setOutStream(ci.getTimingStreamLLVM()); llvm::PassBuilder pb(targetMachine, pto, pgoOpt, &pic); // Attempt to load pass plugins and register their callbacks with PB. @@ -1049,6 +1090,10 @@ void CodeGenAction::runOptimizationPipeline(llvm::raw_pwrite_stream &os) { // Run the passes. mpm.run(*llvmModule, mam); + // Print the timers to the associated output stream and reset them. + if (ci.isTimingEnabled()) + si.getTimePasses().print(); + // Cleanup delete tlii; } @@ -1271,6 +1316,8 @@ void CodeGenAction::executeAction() { const CodeGenOptions &codeGenOpts = ci.getInvocation().getCodeGenOpts(); Fortran::lower::LoweringOptions &loweringOpts = ci.getInvocation().getLoweringOpts(); + mlir::DefaultTimingManager &timingMgr = ci.getTimingManager(); + mlir::TimingScope &timingScopeRoot = ci.getTimingScopeRoot(); // If the output stream is a file, generate it and define the corresponding // output stream. If a pre-defined output stream is available, we will use @@ -1316,6 +1363,11 @@ void CodeGenAction::executeAction() { if (!llvmModule) generateLLVMIR(); + // This will already have been started in generateLLVMIR(). But we need to + // continue operating on the module, so we continue timing it. + mlir::TimingScope timingScopeLLVMIRGen = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdLLVMIRGen, timingMgr)); + // If generating the LLVM module failed, abort! No need for further error // reporting since generateLLVMIR() does this already. if (!llvmModule) @@ -1345,6 +1397,7 @@ void CodeGenAction::executeAction() { // Embed offload objects specified with -fembed-offload-object if (!codeGenOpts.OffloadObjects.empty()) embedOffloadObjects(); + timingScopeLLVMIRGen.stop(); BackendRemarkConsumer remarkConsumer(diags, codeGenOpts); @@ -1373,7 +1426,10 @@ void CodeGenAction::executeAction() { } // Run LLVM's middle-end (i.e. the optimizer). + mlir::TimingScope timingScopeLLVMIRPasses = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdLLVMIRPasses, timingMgr)); runOptimizationPipeline(ci.isOutputStreamNull() ? *os : ci.getOutputStream()); + timingScopeLLVMIRPasses.stop(); if (action == BackendActionTy::Backend_EmitLL || action == BackendActionTy::Backend_EmitBC) { @@ -1382,11 +1438,15 @@ void CodeGenAction::executeAction() { } // Run LLVM's backend and generate either assembly or machine code + mlir::TimingScope timingScopeBackend = timingScopeRoot.nest( + mlir::TimingIdentifier::get(timingIdBackend, timingMgr)); if (action == BackendActionTy::Backend_EmitAssembly || action == BackendActionTy::Backend_EmitObj) { generateMachineCodeOrAssemblyImpl( diags, targetMachine, action, *llvmModule, codeGenOpts, ci.isOutputStreamNull() ? *os : ci.getOutputStream()); + if (timingMgr.isEnabled()) + llvm::reportAndResetTimings(&ci.getTimingStreamCodeGen()); return; } } diff --git a/flang/lib/Support/CMakeLists.txt b/flang/lib/Support/CMakeLists.txt new file mode 100644 index 0000000000000..9c7887aecafbd --- /dev/null +++ b/flang/lib/Support/CMakeLists.txt @@ -0,0 +1,9 @@ +add_flang_library(FortranSupport + Timing.cpp + + LINK_LIBS + MLIRSupport + + LINK_COMPONENTS + Support +) diff --git a/flang/lib/Support/Timing.cpp b/flang/lib/Support/Timing.cpp new file mode 100644 index 0000000000000..ee8309a950eec --- /dev/null +++ b/flang/lib/Support/Timing.cpp @@ -0,0 +1,67 @@ +//===- Timing.cpp - Execution time measurement facilities -----------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Facilities to measure and provide statistics on execution time. +// +//===----------------------------------------------------------------------===// + +#include "flang/Support/Timing.h" +#include "llvm/Support/Format.h" + +class OutputStrategyText : public mlir::OutputStrategy { +protected: + static constexpr llvm::StringLiteral header = "Flang execution timing report"; + +public: + OutputStrategyText(llvm::raw_ostream &os) : mlir::OutputStrategy(os) {} + + void printHeader(const mlir::TimeRecord &total) override { + // Figure out how many spaces to description name. + unsigned padding = (80 - header.size()) / 2; + os << "===" << std::string(73, '-') << "===\n"; + os.indent(padding) << header << '\n'; + os << "===" << std::string(73, '-') << "===\n"; + + // Print the total time followed by the section headers. + os << llvm::format(" Total Execution Time: %.4f seconds\n\n", total.wall); + os << " ----User Time---- ----Wall Time---- ----Name----\n"; + } + + void printFooter() override { os.flush(); } + + void printTime( + const mlir::TimeRecord &time, const mlir::TimeRecord &total) override { + os << llvm::format( + " %8.4f (%5.1f%%)", time.user, 100.0 * time.user / total.user); + os << llvm::format( + " %8.4f (%5.1f%%) ", time.wall, 100.0 * time.wall / total.wall); + } + + void printListEntry(llvm::StringRef name, const mlir::TimeRecord &time, + const mlir::TimeRecord &total, bool lastEntry) override { + printTime(time, total); + os << name << "\n"; + } + + void printTreeEntry(unsigned indent, llvm::StringRef name, + const mlir::TimeRecord &time, const mlir::TimeRecord &total) override { + printTime(time, total); + os.indent(indent) << name << "\n"; + } + + void printTreeEntryEnd(unsigned indent, bool lastEntry) override {} +}; + +namespace Fortran::support { + +std::unique_ptr createTimingFormatterText( + llvm::raw_ostream &os) { + return std::make_unique(os); +} + +} // namespace Fortran::support diff --git a/flang/test/Driver/time-report.f90 b/flang/test/Driver/time-report.f90 new file mode 100644 index 0000000000000..3f6e1e9f87d9a --- /dev/null +++ b/flang/test/Driver/time-report.f90 @@ -0,0 +1,22 @@ +! Check that -ftime-report flag is passed as-is to fc1. The value of the flag +! is only checked there. This behavior intentionally mirrors that of clang. +! +! RUN: %flang -### -c -ftime-report %s 2>&1 | FileCheck %s --check-prefix=CHECK-DRIVER + +! TODO: Currently, detailed timing of LLVM IR optimization and code generation +! passes is not supported. When that is done, add more checks here to make sure +! the output is as expected. + +! RUN: %flang -c -ftime-report -O0 %s 2>&1 | FileCheck %s --check-prefix=CHECK-COMMON +! RUN: %flang -c -ftime-report -O1 %s 2>&1 | FileCheck %s --check-prefix=CHECK-COMMON + +! CHECK-DRIVER: "-ftime-report" + +! CHECK-COMMON: Flang execution timing report +! CHECK-COMMON: MLIR generation +! CHECK-COMMON: MLIR translation/optimization +! CHECK-COMMON: LLVM IR generation +! CHECK-COMMON: LLVM IR optimizations +! CHECK-COMMON: Assembly/Object code generation + +end program