//===- RemarkCounter.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 // //===----------------------------------------------------------------------===// // // Generic tool to count remarks based on properties // //===----------------------------------------------------------------------===// #include "RemarkCounter.h" #include "RemarkUtilRegistry.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Regex.h" using namespace llvm; using namespace remarks; using namespace llvm::remarkutil; static cl::SubCommand CountSub("count", "Collect remarks based on specified criteria."); INPUT_FORMAT_COMMAND_LINE_OPTIONS(CountSub) INPUT_OUTPUT_COMMAND_LINE_OPTIONS(CountSub) static cl::list Keys("args", cl::desc("Specify remark argument/s to count by."), cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional); static cl::list RKeys( "rargs", cl::desc( "Specify remark argument/s to count (accepts regular expressions)."), cl::value_desc("arguments"), cl::sub(CountSub), cl::ValueOptional); static cl::opt RemarkNameOpt("remark-name", cl::desc("Optional remark name to filter collection by."), cl::ValueOptional, cl::sub(CountSub)); static cl::opt PassNameOpt("pass-name", cl::ValueOptional, cl::desc("Optional remark pass name to filter collection by."), cl::sub(CountSub)); static cl::opt RemarkFilterArgByOpt( "filter-arg-by", cl::desc("Optional remark arg to filter collection by."), cl::ValueOptional, cl::sub(CountSub)); static cl::opt RemarkNameOptRE("rremark-name", cl::desc("Optional remark name to filter collection by " "(accepts regular expressions)."), cl::ValueOptional, cl::sub(CountSub)); static cl::opt RemarkArgFilterOptRE("rfilter-arg-by", cl::desc("Optional remark arg to filter collection by " "(accepts regular expressions)."), cl::sub(CountSub), cl::ValueOptional); static cl::opt PassNameOptRE("rpass-name", cl::ValueOptional, cl::desc("Optional remark pass name to filter collection " "by (accepts regular expressions)."), cl::sub(CountSub)); static cl::opt RemarkTypeOpt( "remark-type", cl::desc("Optional remark type to filter collection by."), cl::values(clEnumValN(Type::Unknown, "unknown", "UNKOWN"), clEnumValN(Type::Passed, "passed", "PASSED"), clEnumValN(Type::Missed, "missed", "MISSED"), clEnumValN(Type::Analysis, "analysis", "ANALYSIS"), clEnumValN(Type::AnalysisFPCommute, "analysis-fp-commute", "ANALYSIS_FP_COMMUTE"), clEnumValN(Type::AnalysisAliasing, "analysis-aliasing", "ANALYSIS_ALIASING"), clEnumValN(Type::Failure, "failure", "FAILURE")), cl::init(Type::Failure), cl::sub(CountSub)); static cl::opt CountByOpt( "count-by", cl::desc("Specify the property to collect remarks by."), cl::values( clEnumValN(CountBy::REMARK, "remark-name", "Counts individual remarks based on how many of the remark " "exists."), clEnumValN(CountBy::ARGUMENT, "arg", "Counts based on the value each specified argument has. The " "argument has to have a number value to be considered.")), cl::init(CountBy::REMARK), cl::sub(CountSub)); static cl::opt GroupByOpt( "group-by", cl::desc("Specify the property to group remarks by."), cl::values( clEnumValN( GroupBy::PER_SOURCE, "source", "Display the count broken down by the filepath of each remark " "emitted. Requires remarks to have DebugLoc information."), clEnumValN(GroupBy::PER_FUNCTION, "function", "Breakdown the count by function name."), clEnumValN( GroupBy::PER_FUNCTION_WITH_DEBUG_LOC, "function-with-loc", "Breakdown the count by function name taking into consideration " "the filepath info from the DebugLoc of the remark."), clEnumValN(GroupBy::TOTAL, "total", "Output the total number corresponding to the count for the " "provided input file.")), cl::init(GroupBy::PER_SOURCE), cl::sub(CountSub)); /// Look for matching argument with \p Key in \p Remark and return the parsed /// integer value or 0 if it is has no integer value. static unsigned getValForKey(StringRef Key, const Remark &Remark) { auto *RemarkArg = find_if(Remark.Args, [&Key](const Argument &Arg) { return Arg.Key == Key && Arg.isValInt(); }); if (RemarkArg == Remark.Args.end()) return 0; return *RemarkArg->getValAsInt(); } Error Filters::regexArgumentsValid() { if (RemarkNameFilter && RemarkNameFilter->IsRegex) if (auto E = checkRegex(RemarkNameFilter->FilterRE)) return E; if (PassNameFilter && PassNameFilter->IsRegex) if (auto E = checkRegex(PassNameFilter->FilterRE)) return E; if (ArgFilter && ArgFilter->IsRegex) if (auto E = checkRegex(ArgFilter->FilterRE)) return E; return Error::success(); } bool Filters::filterRemark(const Remark &Remark) { if (RemarkNameFilter && !RemarkNameFilter->match(Remark.RemarkName)) return false; if (PassNameFilter && !PassNameFilter->match(Remark.PassName)) return false; if (RemarkTypeFilter) return *RemarkTypeFilter == Remark.RemarkType; if (ArgFilter) { if (!any_of(Remark.Args, [this](Argument Arg) { return ArgFilter->match(Arg.Val); })) return false; } return true; } Error ArgumentCounter::getAllMatchingArgumentsInRemark( StringRef Buffer, ArrayRef Arguments, Filters &Filter) { auto MaybeParser = createRemarkParser(InputFormat, Buffer); if (!MaybeParser) return MaybeParser.takeError(); auto &Parser = **MaybeParser; auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) { auto &Remark = **MaybeRemark; // Only collect keys from remarks included in the filter. if (!Filter.filterRemark(Remark)) continue; for (auto &Key : Arguments) { for (Argument Arg : Remark.Args) if (Key.match(Arg.Key) && Arg.isValInt()) ArgumentSetIdxMap.insert({Arg.Key, ArgumentSetIdxMap.size()}); } } auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); return Error::success(); } std::optional Counter::getGroupByKey(const Remark &Remark) { switch (Group) { case GroupBy::PER_FUNCTION: return Remark.FunctionName.str(); case GroupBy::TOTAL: return "Total"; case GroupBy::PER_SOURCE: case GroupBy::PER_FUNCTION_WITH_DEBUG_LOC: if (!Remark.Loc.has_value()) return std::nullopt; if (Group == GroupBy::PER_FUNCTION_WITH_DEBUG_LOC) return Remark.Loc->SourceFilePath.str() + ":" + Remark.FunctionName.str(); return Remark.Loc->SourceFilePath.str(); } llvm_unreachable("Fully covered switch above!"); } void ArgumentCounter::collect(const Remark &Remark) { SmallVector Row(ArgumentSetIdxMap.size()); std::optional GroupByKey = getGroupByKey(Remark); // Early return if we don't have a value if (!GroupByKey) return; auto GroupVal = *GroupByKey; CountByKeysMap.insert({GroupVal, Row}); for (auto [Key, Idx] : ArgumentSetIdxMap) { auto Count = getValForKey(Key, Remark); CountByKeysMap[GroupVal][Idx] += Count; } } void RemarkCounter::collect(const Remark &Remark) { std::optional Key = getGroupByKey(Remark); if (!Key.has_value()) return; auto Iter = CountedByRemarksMap.insert({*Key, 1}); if (!Iter.second) Iter.first->second += 1; } Error ArgumentCounter::print(StringRef OutputFileName) { auto MaybeOF = getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); OF->os() << groupByToStr(Group) << ","; unsigned Idx = 0; for (auto [Key, _] : ArgumentSetIdxMap) { OF->os() << Key; if (Idx != ArgumentSetIdxMap.size() - 1) OF->os() << ","; Idx++; } OF->os() << "\n"; for (auto [Header, CountVector] : CountByKeysMap) { OF->os() << Header << ","; unsigned Idx = 0; for (auto Count : CountVector) { OF->os() << Count; if (Idx != ArgumentSetIdxMap.size() - 1) OF->os() << ","; Idx++; } OF->os() << "\n"; } return Error::success(); } Error RemarkCounter::print(StringRef OutputFileName) { auto MaybeOF = getOutputFileWithFlags(OutputFileName, sys::fs::OF_TextWithCRLF); if (!MaybeOF) return MaybeOF.takeError(); auto OF = std::move(*MaybeOF); OF->os() << groupByToStr(Group) << "," << "Count\n"; for (auto [Key, Count] : CountedByRemarksMap) OF->os() << Key << "," << Count << "\n"; OF->keep(); return Error::success(); } Expected getRemarkFilter() { // Create Filter properties. std::optional RemarkNameFilter; std::optional PassNameFilter; std::optional RemarkArgFilter; std::optional RemarkType; if (!RemarkNameOpt.empty()) RemarkNameFilter = {RemarkNameOpt, false}; else if (!RemarkNameOptRE.empty()) RemarkNameFilter = {RemarkNameOptRE, true}; if (!PassNameOpt.empty()) PassNameFilter = {PassNameOpt, false}; else if (!PassNameOptRE.empty()) PassNameFilter = {PassNameOptRE, true}; if (RemarkTypeOpt != Type::Failure) RemarkType = RemarkTypeOpt; if (!RemarkFilterArgByOpt.empty()) RemarkArgFilter = {RemarkFilterArgByOpt, false}; else if (!RemarkArgFilterOptRE.empty()) RemarkArgFilter = {RemarkArgFilterOptRE, true}; // Create RemarkFilter. return Filters::createRemarkFilter(std::move(RemarkNameFilter), std::move(PassNameFilter), std::move(RemarkArgFilter), RemarkType); } Error useCollectRemark(StringRef Buffer, Counter &Counter, Filters &Filter) { // Create Parser. auto MaybeParser = createRemarkParser(InputFormat, Buffer); if (!MaybeParser) return MaybeParser.takeError(); auto &Parser = **MaybeParser; auto MaybeRemark = Parser.next(); for (; MaybeRemark; MaybeRemark = Parser.next()) { const Remark &Remark = **MaybeRemark; if (Filter.filterRemark(Remark)) Counter.collect(Remark); } if (auto E = Counter.print(OutputFileName)) return E; auto E = MaybeRemark.takeError(); if (!E.isA()) return E; consumeError(std::move(E)); return Error::success(); } static Error collectRemarks() { // Create a parser for the user-specified input format. auto MaybeBuf = getInputMemoryBuffer(InputFileName); if (!MaybeBuf) return MaybeBuf.takeError(); StringRef Buffer = (*MaybeBuf)->getBuffer(); auto MaybeFilter = getRemarkFilter(); if (!MaybeFilter) return MaybeFilter.takeError(); auto &Filter = *MaybeFilter; if (CountByOpt == CountBy::REMARK) { RemarkCounter RC(GroupByOpt); if (auto E = useCollectRemark(Buffer, RC, Filter)) return E; } else if (CountByOpt == CountBy::ARGUMENT) { SmallVector ArgumentsVector; if (!Keys.empty()) { for (auto &Key : Keys) ArgumentsVector.push_back({Key, false}); } else if (!RKeys.empty()) for (auto Key : RKeys) ArgumentsVector.push_back({Key, true}); else ArgumentsVector.push_back({".*", true}); Expected AC = ArgumentCounter::createArgumentCounter( GroupByOpt, ArgumentsVector, Buffer, Filter); if (!AC) return AC.takeError(); if (auto E = useCollectRemark(Buffer, *AC, Filter)) return E; } return Error::success(); } static CommandRegistration CountReg(&CountSub, collectRemarks);