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
25using namespace llvm;
26using namespace remarks;
27using namespace llvm::remarkutil;
28
29namespace summary {
30
31static cl::SubCommand
32 SummarySub("summary", "Summarize remarks using different strategies.");
33
34INPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub)
35OUTPUT_FORMAT_COMMAND_LINE_OPTIONS(SummarySub)
36INPUT_OUTPUT_COMMAND_LINE_OPTIONS(SummarySub)
37
38static cl::OptionCategory SummaryStrategyCat("Strategy options");
39
40enum class KeepMode { None, Used, All };
41
42static 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
51static 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.
58static 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.
64class SummaryStrategy {
65public:
66 virtual ~SummaryStrategy() = default;
67
68 /// Strategy should return true if it wants to process the remark \p R.
69 virtual bool filter(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 process(Remark &R) = 0;
76
77 /// Hook to emit new remarks based on the collected data.
78 virtual void emit(RemarkSerializer &Serializer) = 0;
79};
80
81/// Check if any summary strategy options are explicitly enabled.
82static 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
93class 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 filter(Remark &R) override {
129 return R.PassName == "inline" && R.RemarkName != "Summary";
130 }
131
132 Error process(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 emit(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
190static 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 MaybeRemark = Parser.next();
218 for (; MaybeRemark; MaybeRemark = Parser.next()) {
219 Remark &Remark = **MaybeRemark;
220 bool UsedRemark = 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
251static CommandRegistration SummaryReg(&SummarySub, trySummary);
252
253} // namespace summary
254