| 1 | //===- RemarkSummary.cpp --------------------------------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Specialized tool to summarize remarks |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "RemarkUtilHelpers.h" |
| 14 | #include "RemarkUtilRegistry.h" |
| 15 | |
| 16 | #include "llvm/ADT/STLExtras.h" |
| 17 | #include "llvm/ADT/StringRef.h" |
| 18 | #include "llvm/Support/CommandLine.h" |
| 19 | #include "llvm/Support/Debug.h" |
| 20 | #include "llvm/Support/Error.h" |
| 21 | #include "llvm/Support/Regex.h" |
| 22 | #include "llvm/Support/WithColor.h" |
| 23 | #include <memory> |
| 24 | |
| 25 | using namespace llvm; |
| 26 | using namespace remarks; |
| 27 | using namespace llvm::remarkutil; |
| 28 | |
| 29 | namespace summary { |
| 30 | |
| 31 | static cl::SubCommand |
| 32 | SummarySub("summary" , "Summarize remarks using different strategies." ); |
| 33 | |
| 34 | INPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub) |
| 35 | OUTPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub) |
| 36 | INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SummarySub) |
| 37 | |
| 38 | static cl::OptionCategory SummaryStrategyCat("Strategy options" ); |
| 39 | |
| 40 | enum class KeepMode { None, Used, All }; |
| 41 | |
| 42 | static cl::opt<KeepMode> KeepInputOpt( |
| 43 | "keep" , cl::desc("Keep input remarks in output" ), cl::init(Val: KeepMode::None), |
| 44 | cl::values(clEnumValN(KeepMode::None, "none" , |
| 45 | "Don't keep input remarks (default)" ), |
| 46 | clEnumValN(KeepMode::Used, "used" , |
| 47 | "Keep only remarks used for summary" ), |
| 48 | clEnumValN(KeepMode::All, "all" , "Keep all input remarks" )), |
| 49 | cl::sub(SummarySub)); |
| 50 | |
| 51 | static cl::opt<bool> |
| 52 | IgnoreMalformedOpt("ignore-malformed" , |
| 53 | cl::desc("Ignore remarks that fail to process" ), |
| 54 | cl::init(Val: false), cl::Hidden, cl::sub(SummarySub)); |
| 55 | |
| 56 | // Use one cl::opt per Strategy, because future strategies might need to take |
| 57 | // per-strategy parameters. |
| 58 | static cl::opt<bool> EnableInlineSummaryOpt( |
| 59 | "inline-callees" , cl::desc("Summarize per-callee inling statistics" ), |
| 60 | cl::cat(SummaryStrategyCat), cl::init(Val: false), cl::sub(SummarySub)); |
| 61 | |
| 62 | /// An interface to implement different strategies for creating remark |
| 63 | /// summaries. Override this class to develop new strategies. |
| 64 | class SummaryStrategy { |
| 65 | public: |
| 66 | virtual ~SummaryStrategy() = default; |
| 67 | |
| 68 | /// Strategy should return true if it wants to process the remark \p R. |
| 69 | virtual bool (Remark &R) = 0; |
| 70 | |
| 71 | /// Hook to process the remark \p R (i.e. collect the necessary data for |
| 72 | /// producing summary remarks). This will only be called with remarks |
| 73 | /// accepted by filter(). Can return an error if \p R is malformed or |
| 74 | /// unexpected. |
| 75 | virtual Error (Remark &R) = 0; |
| 76 | |
| 77 | /// Hook to emit new remarks based on the collected data. |
| 78 | virtual void (RemarkSerializer &Serializer) = 0; |
| 79 | }; |
| 80 | |
| 81 | /// Check if any summary strategy options are explicitly enabled. |
| 82 | static bool isAnyStrategyRequested() { |
| 83 | for (auto &[_, Opt] : cl::getRegisteredOptions(Sub&: SummarySub)) { |
| 84 | if (!is_contained(Range&: Opt->Categories, Element: &SummaryStrategyCat)) |
| 85 | continue; |
| 86 | if (!Opt->getNumOccurrences()) |
| 87 | continue; |
| 88 | return true; |
| 89 | } |
| 90 | return false; |
| 91 | } |
| 92 | |
| 93 | class InlineCalleeSummary : public SummaryStrategy { |
| 94 | struct CallsiteCost { |
| 95 | int Cost = 0; |
| 96 | int Threshold = 0; |
| 97 | std::optional<RemarkLocation> Loc; |
| 98 | |
| 99 | int getProfit() const { return Threshold - Cost; } |
| 100 | |
| 101 | friend bool operator==(const CallsiteCost &A, const CallsiteCost &B) { |
| 102 | return A.Cost == B.Cost && A.Threshold == B.Threshold && A.Loc == B.Loc; |
| 103 | } |
| 104 | |
| 105 | friend bool operator!=(const CallsiteCost &A, const CallsiteCost &B) { |
| 106 | return !(A == B); |
| 107 | } |
| 108 | }; |
| 109 | |
| 110 | struct CalleeSummary { |
| 111 | SmallDenseMap<StringRef, size_t> Stats; |
| 112 | std::optional<RemarkLocation> Loc; |
| 113 | std::optional<CallsiteCost> LeastProfit; |
| 114 | std::optional<CallsiteCost> MostProfit; |
| 115 | |
| 116 | void updateCost(CallsiteCost NewCost) { |
| 117 | if (!LeastProfit || NewCost.getProfit() < LeastProfit->getProfit()) |
| 118 | LeastProfit = NewCost; |
| 119 | if (!MostProfit || NewCost.getProfit() > MostProfit->getProfit()) |
| 120 | MostProfit = NewCost; |
| 121 | } |
| 122 | }; |
| 123 | |
| 124 | DenseMap<StringRef, CalleeSummary> Callees; |
| 125 | |
| 126 | Error malformed() { return createStringError(Fmt: "Malformed inline remark." ); } |
| 127 | |
| 128 | bool (Remark &R) override { |
| 129 | return R.PassName == "inline" && R.RemarkName != "Summary" ; |
| 130 | } |
| 131 | |
| 132 | Error (Remark &R) override { |
| 133 | auto *CalleeArg = R.getArgByKey(Key: "Callee" ); |
| 134 | if (!CalleeArg) |
| 135 | return Error::success(); |
| 136 | auto &Callee = Callees[CalleeArg->Val]; |
| 137 | ++Callee.Stats[R.RemarkName]; |
| 138 | if (!Callee.Loc) |
| 139 | Callee.Loc = CalleeArg->Loc; |
| 140 | |
| 141 | Argument *CostArg = R.getArgByKey(Key: "Cost" ); |
| 142 | Argument *ThresholdArg = R.getArgByKey(Key: "Threshold" ); |
| 143 | if (!CostArg || !ThresholdArg) |
| 144 | return Error::success(); |
| 145 | auto CostVal = CostArg->getValAsInt<int>(); |
| 146 | auto ThresholdVal = ThresholdArg->getValAsInt<int>(); |
| 147 | if (!CostVal || !ThresholdVal) |
| 148 | return malformed(); |
| 149 | Callee.updateCost(NewCost: {.Cost: *CostVal, .Threshold: *ThresholdVal, .Loc: R.Loc}); |
| 150 | return Error::success(); |
| 151 | } |
| 152 | |
| 153 | void (RemarkSerializer &Serializer) override { |
| 154 | SmallVector<StringRef> SortedKeys(Callees.keys()); |
| 155 | llvm::sort(C&: SortedKeys); |
| 156 | for (StringRef K : SortedKeys) { |
| 157 | auto &V = Callees[K]; |
| 158 | RemarkBuilder RB(Type::Analysis, "inline" , "Summary" , K); |
| 159 | if (V.Stats.empty()) |
| 160 | continue; |
| 161 | RB.R.Loc = V.Loc; |
| 162 | RB << "Incoming Calls (" ; |
| 163 | SmallVector<StringRef> StatKeys(V.Stats.keys()); |
| 164 | llvm::sort(C&: StatKeys); |
| 165 | bool First = true; |
| 166 | for (StringRef StatK : StatKeys) { |
| 167 | if (!First) |
| 168 | RB << ", " ; |
| 169 | RB << StatK << ": " << NV(StatK, V.Stats[StatK]); |
| 170 | First = false; |
| 171 | } |
| 172 | RB << ")" ; |
| 173 | if (V.LeastProfit && V.MostProfit != V.LeastProfit) { |
| 174 | RB << "\nLeast profitable (cost=" |
| 175 | << NV("LeastProfitCost" , V.LeastProfit->Cost, V.LeastProfit->Loc) |
| 176 | << ", threshold=" |
| 177 | << NV("LeastProfitThreshold" , V.LeastProfit->Threshold) << ")" ; |
| 178 | } |
| 179 | if (V.MostProfit) { |
| 180 | RB << "\nMost profitable (cost=" |
| 181 | << NV("MostProfitCost" , V.MostProfit->Cost, V.MostProfit->Loc) |
| 182 | << ", threshold=" |
| 183 | << NV("MostProfitThreshold" , V.MostProfit->Threshold) << ")" ; |
| 184 | } |
| 185 | Serializer.emit(Remark: RB.R); |
| 186 | } |
| 187 | } |
| 188 | }; |
| 189 | |
| 190 | static Error trySummary() { |
| 191 | auto MaybeBuf = getInputMemoryBuffer(InputFileName); |
| 192 | if (!MaybeBuf) |
| 193 | return MaybeBuf.takeError(); |
| 194 | auto MaybeParser = createRemarkParser(ParserFormat: InputFormat, Buf: (*MaybeBuf)->getBuffer()); |
| 195 | if (!MaybeParser) |
| 196 | return MaybeParser.takeError(); |
| 197 | auto &Parser = **MaybeParser; |
| 198 | |
| 199 | Format SerializerFormat = |
| 200 | getSerializerFormat(OutputFileName, SelectedFormat: OutputFormat, DefaultFormat: Parser.ParserFormat); |
| 201 | |
| 202 | auto MaybeOF = getOutputFileForRemarks(OutputFileName, OutputFormat: SerializerFormat); |
| 203 | if (!MaybeOF) |
| 204 | return MaybeOF.takeError(); |
| 205 | auto OF = std::move(*MaybeOF); |
| 206 | |
| 207 | auto MaybeSerializer = createRemarkSerializer(RemarksFormat: SerializerFormat, OS&: OF->os()); |
| 208 | if (!MaybeSerializer) |
| 209 | return MaybeSerializer.takeError(); |
| 210 | auto &Serializer = **MaybeSerializer; |
| 211 | |
| 212 | bool UseDefaultStrategies = !isAnyStrategyRequested(); |
| 213 | SmallVector<std::unique_ptr<SummaryStrategy>> Strategies; |
| 214 | if (EnableInlineSummaryOpt || UseDefaultStrategies) |
| 215 | Strategies.push_back(Elt: std::make_unique<InlineCalleeSummary>()); |
| 216 | |
| 217 | auto = Parser.next(); |
| 218 | for (; MaybeRemark; MaybeRemark = Parser.next()) { |
| 219 | Remark & = **MaybeRemark; |
| 220 | bool = false; |
| 221 | for (auto &Strategy : Strategies) { |
| 222 | if (!Strategy->filter(R&: Remark)) |
| 223 | continue; |
| 224 | UsedRemark = true; |
| 225 | if (auto E = Strategy->process(R&: Remark)) { |
| 226 | if (IgnoreMalformedOpt) { |
| 227 | WithColor::warning() << "Ignored error: " << E << "\n" ; |
| 228 | consumeError(Err: std::move(E)); |
| 229 | continue; |
| 230 | } |
| 231 | return E; |
| 232 | } |
| 233 | } |
| 234 | if (KeepInputOpt == KeepMode::All || |
| 235 | (KeepInputOpt == KeepMode::Used && UsedRemark)) |
| 236 | Serializer.emit(Remark); |
| 237 | } |
| 238 | |
| 239 | auto E = MaybeRemark.takeError(); |
| 240 | if (!E.isA<EndOfFileError>()) |
| 241 | return E; |
| 242 | consumeError(Err: std::move(E)); |
| 243 | |
| 244 | for (auto &Strategy : Strategies) |
| 245 | Strategy->emit(Serializer); |
| 246 | |
| 247 | OF->keep(); |
| 248 | return Error::success(); |
| 249 | } |
| 250 | |
| 251 | static CommandRegistration SummaryReg(&SummarySub, trySummary); |
| 252 | |
| 253 | } // namespace summary |
| 254 | |