diff --git a/flang/tools/CMakeLists.txt b/flang/tools/CMakeLists.txt --- a/flang/tools/CMakeLists.txt +++ b/flang/tools/CMakeLists.txt @@ -6,6 +6,7 @@ # #===------------------------------------------------------------------------===# +add_subdirectory(flang-omp-report-plugin) add_subdirectory(f18) add_subdirectory(flang-driver) add_subdirectory(tco) diff --git a/flang/tools/flang-omp-report-plugin/CMakeLists.txt b/flang/tools/flang-omp-report-plugin/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/flang/tools/flang-omp-report-plugin/CMakeLists.txt @@ -0,0 +1,6 @@ +add_llvm_library( + flangOmpReport + MODULE + flang-omp-report-plugin.cpp + count-openmp.cpp +) diff --git a/flang/tools/flang-omp-report-plugin/count-openmp.h b/flang/tools/flang-omp-report-plugin/count-openmp.h new file mode 100644 --- /dev/null +++ b/flang/tools/flang-omp-report-plugin/count-openmp.h @@ -0,0 +1,370 @@ +//===-- tools/flang-omp-report-plugin/count-openmp.h ----------------------===// +// +// 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/Common/Fortran-features.h" +#include "flang/Common/default-kinds.h" +#include "flang/Evaluate/expression.h" +#include "flang/Parser/characters.h" +#include "flang/Parser/message.h" +#include "flang/Parser/parse-tree-visitor.h" +#include "flang/Parser/parse-tree.h" +#include "flang/Parser/parsing.h" +#include "flang/Parser/provenance.h" +#include "flang/Parser/source.h" +#include "flang/Parser/unparse.h" +#include "flang/Semantics/expression.h" +#include "flang/Semantics/semantics.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Fortran::parser; +struct SummaryRecord { + int numConstructs; + int numClauses; + friend bool operator==(const SummaryRecord &a, const SummaryRecord &b) { + return a.numConstructs == b.numConstructs && a.numClauses == b.numClauses; + } + friend bool operator!=(const SummaryRecord &a, const SummaryRecord &b) { + return !(a == b); + } +}; +struct ClauseSummary { + std::string clause; + int numClauses; + friend bool operator==(const ClauseSummary &a, const ClauseSummary &b) { + return a.clause == b.clause && a.numClauses == b.numClauses; + } + friend bool operator!=(const ClauseSummary &a, const ClauseSummary &b) { + return !(a == b); + } +}; +struct ConstructSummary { + std::string construct; + int numConstructs; + std::vector clauses; + friend bool operator==(const ConstructSummary &a, const ConstructSummary &b) { + return a.construct == b.construct && a.numConstructs == b.numConstructs && + a.clauses == b.clauses; + } + friend bool operator!=(const ConstructSummary &a, const ConstructSummary &b) { + return !(a == b); + } +}; +struct ClauseInfo { + std::string clause; + std::string clauseDetails; + ClauseInfo() {} + ClauseInfo(const std::string &c, const std::string &cd) + : clause{c}, clauseDetails{cd} {} + ClauseInfo(const std::pair &p) + : clause{std::get<0>(p)}, clauseDetails{std::get<1>(p)} {} + friend bool operator<(const ClauseInfo &a, const ClauseInfo &b) { + return a.clause < b.clause; + } + friend bool operator==(const ClauseInfo &a, const ClauseInfo &b) { + return a.clause == b.clause && a.clauseDetails == b.clauseDetails; + } + friend bool operator!=(const ClauseInfo &a, const ClauseInfo &b) { + return !(a == b); + } +}; +struct LogRecord { + std::string file; + int line; + std::string construct; + std::vector clauses; + friend bool operator==(const LogRecord &a, const LogRecord &b) { + return a.file == b.file && a.line == b.line && a.construct == b.construct && + a.clauses == b.clauses; + } + friend bool operator!=(const LogRecord &a, const LogRecord &b) { + return !(a == b); + } +}; + +using OmpWrapperType = + std::variant; + +struct OpenMPCounterVisitor { + std::string normalize_construct_name(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::tolower(c); }); + return s; + } + ClauseInfo normalize_clause_name(const std::string &s) { + std::size_t start = s.find('('); + std::size_t end = s.find(')'); + std::string clauseName; + if (start != std::string::npos && end != std::string::npos) { + clauseName = s.substr(0, start); + clauseDetails = s.substr(start + 1, end - start - 1); + } else { + clauseName = s; + } + std::transform(clauseName.begin(), clauseName.end(), clauseName.begin(), + [](unsigned char c) { return std::tolower(c); }); + std::transform(clauseDetails.begin(), clauseDetails.end(), + clauseDetails.begin(), [](unsigned char c) { return std::tolower(c); }); + return ClauseInfo{clauseName, clauseDetails}; + } + SourcePosition getLocation(const OmpWrapperType &w) { + if (auto *val = std::get_if(&w)) { + const OpenMPConstruct *o{*val}; + return getLocation(*o); + } + return getLocation(*std::get(w)); + } + SourcePosition getLocation(const OpenMPDeclarativeConstruct &c) { + return std::visit( + [&](const auto &o) -> SourcePosition { + return parsing->allCooked().GetSourcePositionRange(o.source)->first; + }, + c.u); + } + SourcePosition getLocation(const OpenMPConstruct &c) { + return std::visit( + Fortran::common::visitors{ + [&](const OpenMPStandaloneConstruct &c) -> SourcePosition { + return parsing->allCooked() + .GetSourcePositionRange(c.source) + ->first; + }, + // OpenMPSectionsConstruct, OpenMPLoopConstruct, + // OpenMPBlockConstruct, OpenMPCriticalConstruct Get the source from + // the directive field. + [&](const auto &c) -> SourcePosition { + const CharBlock &source{std::get<0>(c.t).source}; + return (parsing->allCooked().GetSourcePositionRange(source)) + ->first; + }, + [&](const OpenMPAtomicConstruct &c) -> SourcePosition { + return std::visit( + [&](const auto &o) -> SourcePosition { + const CharBlock &source{std::get(o.t).source}; + return parsing->allCooked() + .GetSourcePositionRange(source) + ->first; + }, + c.u); + }, + }, + c.u); + } + + std::string getName(const OmpWrapperType &w) { + if (auto *val = std::get_if(&w)) { + const OpenMPConstruct *o{*val}; + return getName(*o); + } + return getName(*std::get(w)); + } + std::string getName(const OpenMPDeclarativeConstruct &c) { + return std::visit( + [&](const auto &o) -> std::string { + const CharBlock &source{std::get(o.t).source}; + return normalize_construct_name(source.ToString()); + }, + c.u); + } + std::string getName(const OpenMPConstruct &c) { + return std::visit( + Fortran::common::visitors{ + [&](const OpenMPStandaloneConstruct &c) -> std::string { + return std::visit( + [&](const auto &c) { + // Get source from the directive or verbatim fields + const CharBlock &source{std::get<0>(c.t).source}; + return normalize_construct_name(source.ToString()); + }, + c.u); + }, + [&](const OpenMPExecutableAllocate &c) -> std::string { + const CharBlock &source{std::get<0>(c.t).source}; + return normalize_construct_name(source.ToString()); + }, + [&](const OpenMPDeclarativeAllocate &c) -> std::string { + const CharBlock &source{std::get<0>(c.t).source}; + return normalize_construct_name(source.ToString()); + }, + [&](const OpenMPAtomicConstruct &c) -> std::string { + return std::visit( + [&](const auto &c) { + // Get source from the verbatim fields + const CharBlock &source{std::get(c.t).source}; + return "atomic-" + + normalize_construct_name(source.ToString()); + }, + c.u); + }, + // OpenMPSectionsConstruct, OpenMPLoopConstruct, + // OpenMPBlockConstruct, OpenMPCriticalConstruct Get the source from + // the directive field of the begin directive or from the verbatim + // field of the begin directive in Critical + [&](const auto &c) -> std::string { + const CharBlock &source{std::get<0>(std::get<0>(c.t).t).source}; + return normalize_construct_name(source.ToString()); + }, + }, + c.u); + } + + template bool Pre(const A &) { return true; } + template void Post(const A &) {} + bool Pre(const OpenMPDeclarativeConstruct &c) { + OmpWrapperType *ow{new OmpWrapperType(&c)}; + ompWrapperStack.push_back(ow); + return true; + } + bool Pre(const OpenMPConstruct &c) { + OmpWrapperType *ow{new OmpWrapperType(&c)}; + ompWrapperStack.push_back(ow); + return true; + } + bool Pre(const OmpEndLoopDirective &c) { + isEndLoopDirective = true; + return true; + } + bool Pre(const DoConstruct &) { + loopLogRecordStack.push_back(curLoopLogRecord); + return true; + } + + void Post(const OpenMPDeclarativeConstruct &) { PostConstructsCommon(); } + void Post(const OpenMPConstruct &) { PostConstructsCommon(); } + void PostConstructsCommon() { + ++constructCounter; + OmpWrapperType *curConstruct = ompWrapperStack.back(); + ompConstructCounter[normalize_construct_name(getName(*curConstruct))]++; + std::sort( + clauseStrings[curConstruct].begin(), clauseStrings[curConstruct].end()); + + SourcePosition s{getLocation(*curConstruct)}; + LogRecord r{s.file.path(), s.line, getName(*curConstruct), + clauseStrings[curConstruct]}; + constructClauses.push_back(r); + + // Keep track of loop log records if it can potentially have the + // nowait clause added on later. + if (const auto *oc = std::get_if(curConstruct)) { + if (const auto *olc = std::get_if(&(*oc)->u)) { + const auto &beginLoopDir{ + std::get(olc->t)}; + const auto &beginDir{ + std::get(beginLoopDir.t)}; + if (beginDir.v == llvm::omp::Directive::OMPD_do || + beginDir.v == llvm::omp::Directive::OMPD_do_simd) { + curLoopLogRecord = &constructClauses.back(); + } + } + } + + auto it = clauseStrings.find(curConstruct); + clauseStrings.erase(it); + ompWrapperStack.pop_back(); + delete curConstruct; + } + void Post(const OmpEndLoopDirective &c) { isEndLoopDirective = false; } + + void Post(const OmpProcBindClause::Type &c) { + clauseDetails += "type=" + OmpProcBindClause::EnumToString(c) + ";"; + } + void Post(const OmpDefaultClause::Type &c) { + clauseDetails += "type=" + OmpDefaultClause::EnumToString(c) + ";"; + } + void Post(const OmpDefaultmapClause::ImplicitBehavior &c) { + clauseDetails += + "implicit_behavior=" + OmpDefaultmapClause::EnumToString(c) + ";"; + } + void Post(const OmpDefaultmapClause::VariableCategory &c) { + clauseDetails += + "variable_category=" + OmpDefaultmapClause::EnumToString(c) + ";"; + } + void Post(const OmpScheduleModifierType::ModType &c) { + clauseDetails += + "modifier=" + OmpScheduleModifierType::EnumToString(c) + ";"; + } + void Post(const OmpLinearModifier::Type &c) { + clauseDetails += "modifier=" + OmpLinearModifier::EnumToString(c) + ";"; + } + void Post(const OmpDependenceType::Type &c) { + clauseDetails += "type=" + OmpDependenceType::EnumToString(c) + ";"; + } + void Post(const OmpMapType::Type &c) { + clauseDetails += "type=" + OmpMapType::EnumToString(c) + ";"; + } + void Post(const OmpScheduleClause::ScheduleType &c) { + clauseDetails += "type=" + OmpScheduleClause::EnumToString(c) + ";"; + } + void Post(const OmpIfClause::DirectiveNameModifier &c) { + clauseDetails += "name_modifier=" + OmpIfClause::EnumToString(c) + ";"; + } + void Post(const OmpCancelType::Type &c) { + clauseDetails += "type=" + OmpCancelType::EnumToString(c) + ";"; + } + void Post(const OmpClause &c) { + PostClauseCommon(normalize_clause_name(c.source.ToString())); + clauseDetails.clear(); + } + void PostClauseCommon(const ClauseInfo &ci) { + ++clauseCounter; + ompClauseCounter[ci.clause]++; + // The end loop construct (!$omp end do) can contain a nowait clause. + // The flang parser does not parse the end loop construct as part of + // the OpenMP construct for the loop construct. So the end loop is left + // hanging as a separate executable construct. If a nowait clause is seen in + // an end loop construct we have to find the associated loop construct and + // add nowait to its list of clauses. Note: This is not a bug in flang, the + // parse tree is corrected during semantic analysis. + if (ci.clause == "nowait" && isEndLoopDirective) { + assert(curLoopLogRecord && + "loop Construct should be visited before a nowait clause"); + constructClauseCount[std::make_pair( + curLoopLogRecord->construct, ci.clause)]++; + curLoopLogRecord->clauses.push_back(ci); + } else { + assert(!ompWrapperStack.empty() && + "Construct should be visited before clause"); + constructClauseCount[std::make_pair( + getName(*ompWrapperStack.back()), ci.clause)]++; + clauseStrings[ompWrapperStack.back()].push_back(ci); + } + } + void Post(const DoConstruct &) { + curLoopLogRecord = loopLogRecordStack.back(); + loopLogRecordStack.pop_back(); + } + + int clauseCounter{0}, constructCounter{0}; + std::string clauseDetails{""}; + std::map ompConstructCounter; + std::map ompClauseCounter; + std::map, int> constructClauseCount; + std::deque + constructClauses; // curLoopLogRecord and loopLogRecordStack store + // pointers to this datastructure's entries. Hence a + // vector cannot be used since pointers are invalidated + // on resize. Next best option seems to be deque. Also a + // list cannot be used since YAML gen requires a + // datastructure which can be accessed through indices. + LogRecord *curLoopLogRecord{nullptr}; + std::vector loopLogRecordStack; + std::vector ompWrapperStack; + std::map> clauseStrings; + bool isEndLoopDirective{false}; + Parsing *parsing{nullptr}; +}; + +void OpenMPStatisticsParseTree(Parsing &parse); +void SummarizeResults(); diff --git a/flang/tools/flang-omp-report-plugin/count-openmp.cpp b/flang/tools/flang-omp-report-plugin/count-openmp.cpp new file mode 100644 --- /dev/null +++ b/flang/tools/flang-omp-report-plugin/count-openmp.cpp @@ -0,0 +1,210 @@ +//===-- tools/flang-omp-report-plugin/count-openmp.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 "count-openmp.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLParser.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +OpenMPCounterVisitor visitor; +void OpenMPStatisticsParseTree(Fortran::parser::Parsing &parsing) { + visitor.parsing = &parsing; + const Program &parseTree{*parsing.parseTree()}; + Fortran::parser::Walk(parseTree, visitor); +} + +static void getSortedConstructSummary( + std::vector &csv, OpenMPCounterVisitor &visitor) { + typedef std::function + CmpFuncP; + CmpFuncP cmpCS = [](const ConstructSummary &a, const ConstructSummary &b) { + // Sort by: + return ( + // 1. Number of times constructs appear + (a.numConstructs > b.numConstructs) || + // 2. If 1. gives equality, compare the number of clauses + ((a.numConstructs == b.numConstructs) && + (a.clauses.size() > b.clauses.size())) || + // 3. If 1. and 2. give equality, compare names (lexicographically) + (((a.numConstructs == b.numConstructs) && + (a.clauses.size() == b.clauses.size()) && + (a.construct >= b.construct)))); + }; + + for (auto &p : visitor.ompConstructCounter) { + ConstructSummary cs; + cs.construct = p.first; + cs.numConstructs = p.second; + for (auto elem : visitor.constructClauseCount) { + if (p.first == elem.first.first) { + ClauseSummary c{elem.first.second, elem.second}; + cs.clauses.emplace_back(c); + } + } + csv.push_back(cs); + } + std::stable_sort(csv.begin(), csv.end(), cmpCS); +} + +static void getSortedClauseSummary( + std::vector &csv, OpenMPCounterVisitor &visitor) { + typedef std::function + CmpFuncCS; + CmpFuncCS cmpCS = [](const ClauseSummary &a, const ClauseSummary &b) { + // Sort by: + return ( + // 1. Number of times this clause is present + (a.numClauses > b.numClauses) || + // 2. If 1. gives equality, compare names (lexicographically) + ((a.numClauses == b.numClauses) && (a.clause > b.clause))); + }; + for (auto &p : visitor.ompClauseCounter) { + ClauseSummary cs; + cs.clause = p.first; + cs.numClauses = p.second; + csv.push_back(cs); + } + std::stable_sort(csv.begin(), csv.end(), cmpCS); +} + +class Dumper { + void openStream(llvm::StringRef fileName) { + std::error_code EC; + OS = new llvm::raw_fd_ostream( + fileName, EC, llvm::sys::fs::OpenFlags::OF_Text); + if (EC) { + llvm::errs() << "Opening file = " << fileName << "\n"; + } + } + + void closeStream() { delete OS; } + + std::string getFileName(const std::string &fileSuffix) { + std::string retStr{"tmp"}; + std::string fileExt{".yaml"}; + return retStr + fileSuffix + fileExt; + } + + template void dump(llvm::StringRef fileName, T &r) { + openStream(fileName); + llvm::yaml::Output yout(*OS); + yout << r; + closeStream(); + } + + llvm::raw_ostream *OS; + +public: + void dump(SummaryRecord &sr, std::vector &cosv, + std::vector &clsv, std::deque &lrv) { + dump(getFileName("-log"), lrv); + } +}; + +std::istream &operator>>(std::istream &is, std::deque &vlr) { + std::string ignore_line; + std::getline(is, ignore_line); + + LogRecord lr; + while (std::getline(is, lr.file, ',')) { + lr.clauses.clear(); + std::string tmp; + std::getline(is, tmp, ','); + lr.line = std::stoi(tmp); + std::string tmp2; + std::getline(is, tmp2); + size_t commaLoc{tmp2.find(',')}; + if (commaLoc != std::string::npos) { + lr.construct = tmp2.substr(0, commaLoc); + std::string clauses_str{ + tmp2.substr(commaLoc + 1, tmp2.length() - commaLoc)}; + std::istringstream iss{clauses_str}; + std::string clause; + while (std::getline(iss, clause, ',')) { + std::string clauseDetails; + std::getline(iss, clauseDetails, ','); + if (clauseDetails.empty()) + lr.clauses.push_back(std::make_pair(clause, clauseDetails)); + else + lr.clauses.push_back(std::make_pair( + clause, clauseDetails.substr(1, clauseDetails.length() - 2))); + } + } else { + lr.construct = tmp2; + } + vlr.push_back(lr); + } + return is; +} + +LLVM_YAML_IS_SEQUENCE_VECTOR(ConstructSummary) +LLVM_YAML_IS_SEQUENCE_VECTOR(ClauseSummary) +LLVM_YAML_IS_SEQUENCE_VECTOR(LogRecord) +LLVM_YAML_IS_SEQUENCE_VECTOR(ClauseInfo) +namespace llvm { +namespace yaml { +using llvm::yaml::IO; +using llvm::yaml::MappingTraits; +template +struct SequenceTraits, + std::enable_if_t::flow>::value>> + : SequenceTraitsImpl, SequenceElementTraits::flow> {}; +template <> struct MappingTraits { + static void mapping(IO &io, SummaryRecord &info) { + io.mapRequired("numConstructs", info.numConstructs); + io.mapRequired("numClauses", info.numClauses); + } +}; +template <> struct MappingTraits { + static void mapping(IO &io, ClauseSummary &info) { + io.mapRequired("clause", info.clause); + io.mapRequired("numClauses", info.numClauses); + } +}; +template <> struct MappingTraits { + static void mapping(IO &io, ConstructSummary &info) { + io.mapRequired("construct", info.construct); + io.mapRequired("numConstructs", info.numConstructs); + io.mapRequired("clauses", info.clauses); + } +}; +template <> struct MappingTraits { + static void mapping(IO &io, ClauseInfo &info) { + io.mapRequired("clause", info.clause); + io.mapRequired("details", info.clauseDetails); + } +}; +template <> struct MappingTraits { + static void mapping(IO &io, LogRecord &info) { + io.mapRequired("file", info.file); + io.mapRequired("line", info.line); + io.mapRequired("construct", info.construct); + io.mapRequired("clauses", info.clauses); + } +}; +} // namespace yaml +} // namespace llvm + +void SummarizeResults() { + SummaryRecord sr{visitor.constructCounter, visitor.clauseCounter}; + std::vector cosv; + getSortedConstructSummary(cosv, visitor); + std::vector clsv; + getSortedClauseSummary(clsv, visitor); + { + std::unique_ptr d = std::make_unique(); + d->dump(sr, cosv, clsv, visitor.constructClauses); + } +} diff --git a/flang/tools/flang-omp-report-plugin/flang-omp-report-plugin.cpp b/flang/tools/flang-omp-report-plugin/flang-omp-report-plugin.cpp new file mode 100644 --- /dev/null +++ b/flang/tools/flang-omp-report-plugin/flang-omp-report-plugin.cpp @@ -0,0 +1,19 @@ +#include "count-openmp.h" + +#include "flang/Frontend/CompilerInstance.h" +#include "flang/Frontend/FrontendActions.h" +#include "flang/Frontend/FrontendPluginRegistry.h" +#include "flang/Parser/dump-parse-tree.h" +#include "flang/Parser/parsing.h" + +using namespace Fortran::frontend; + +class FompReportYamlAction : public PluginParseTreeAction { + void ExecuteAction() override { + OpenMPStatisticsParseTree(instance().parsing()); + SummarizeResults(); + } +}; + +static FrontendPluginRegistry::Add X( + "fomp-report-yaml", "Flang omp report tool - output YAML format");