1 | //===------------------ llvm-opt-report/OptReport.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 | /// \file |
10 | /// This file implements a tool that can parse the YAML optimization |
11 | /// records and generate an optimization summary annotated source listing |
12 | /// report. |
13 | /// |
14 | //===----------------------------------------------------------------------===// |
15 | |
16 | #include "llvm-c/Remarks.h" |
17 | #include "llvm/ADT/StringExtras.h" |
18 | #include "llvm/Demangle/Demangle.h" |
19 | #include "llvm/Remarks/Remark.h" |
20 | #include "llvm/Remarks/RemarkFormat.h" |
21 | #include "llvm/Remarks/RemarkParser.h" |
22 | #include "llvm/Support/CommandLine.h" |
23 | #include "llvm/Support/Error.h" |
24 | #include "llvm/Support/ErrorOr.h" |
25 | #include "llvm/Support/FileSystem.h" |
26 | #include "llvm/Support/Format.h" |
27 | #include "llvm/Support/InitLLVM.h" |
28 | #include "llvm/Support/LineIterator.h" |
29 | #include "llvm/Support/MemoryBuffer.h" |
30 | #include "llvm/Support/Path.h" |
31 | #include "llvm/Support/Program.h" |
32 | #include "llvm/Support/TypeSize.h" |
33 | #include "llvm/Support/WithColor.h" |
34 | #include "llvm/Support/raw_ostream.h" |
35 | #include <cstdlib> |
36 | #include <map> |
37 | #include <optional> |
38 | #include <set> |
39 | |
40 | using namespace llvm; |
41 | |
42 | // Mark all our options with this category, everything else (except for -version |
43 | // and -help) will be hidden. |
44 | static cl::OptionCategory |
45 | OptReportCategory("llvm-opt-report options" ); |
46 | |
47 | static cl::opt<std::string> |
48 | InputFileName(cl::Positional, cl::desc("<input>" ), cl::init(Val: "-" ), |
49 | cl::cat(OptReportCategory)); |
50 | |
51 | static cl::opt<std::string> |
52 | OutputFileName("o" , cl::desc("Output file" ), cl::init(Val: "-" ), |
53 | cl::cat(OptReportCategory)); |
54 | |
55 | static cl::opt<std::string> |
56 | InputRelDir("r" , cl::desc("Root for relative input paths" ), cl::init(Val: "" ), |
57 | cl::cat(OptReportCategory)); |
58 | |
59 | static cl::opt<bool> |
60 | Succinct("s" , cl::desc("Don't include vectorization factors, etc." ), |
61 | cl::init(Val: false), cl::cat(OptReportCategory)); |
62 | |
63 | static cl::opt<bool> |
64 | NoDemangle("no-demangle" , cl::desc("Don't demangle function names" ), |
65 | cl::init(Val: false), cl::cat(OptReportCategory)); |
66 | |
67 | static cl::opt<std::string> ParserFormat("format" , |
68 | cl::desc("The format of the remarks." ), |
69 | cl::init(Val: "yaml" ), |
70 | cl::cat(OptReportCategory)); |
71 | |
72 | namespace { |
73 | // For each location in the source file, the common per-transformation state |
74 | // collected. |
75 | struct OptReportLocationItemInfo { |
76 | bool Analyzed = false; |
77 | bool Transformed = false; |
78 | |
79 | OptReportLocationItemInfo &operator |= ( |
80 | const OptReportLocationItemInfo &RHS) { |
81 | Analyzed |= RHS.Analyzed; |
82 | Transformed |= RHS.Transformed; |
83 | |
84 | return *this; |
85 | } |
86 | |
87 | bool operator < (const OptReportLocationItemInfo &RHS) const { |
88 | if (Analyzed < RHS.Analyzed) |
89 | return true; |
90 | else if (Analyzed > RHS.Analyzed) |
91 | return false; |
92 | else if (Transformed < RHS.Transformed) |
93 | return true; |
94 | return false; |
95 | } |
96 | }; |
97 | |
98 | // The per-location information collected for producing an optimization report. |
99 | struct OptReportLocationInfo { |
100 | OptReportLocationItemInfo Inlined; |
101 | OptReportLocationItemInfo Unrolled; |
102 | OptReportLocationItemInfo Vectorized; |
103 | |
104 | ElementCount VectorizationFactor = ElementCount::getFixed(MinVal: 1); |
105 | int InterleaveCount = 1; |
106 | int UnrollCount = 1; |
107 | |
108 | OptReportLocationInfo &operator |= (const OptReportLocationInfo &RHS) { |
109 | Inlined |= RHS.Inlined; |
110 | Unrolled |= RHS.Unrolled; |
111 | Vectorized |= RHS.Vectorized; |
112 | |
113 | if (ElementCount::isKnownLT(LHS: VectorizationFactor, RHS: RHS.VectorizationFactor)) |
114 | VectorizationFactor = RHS.VectorizationFactor; |
115 | |
116 | InterleaveCount = std::max(a: InterleaveCount, b: RHS.InterleaveCount); |
117 | UnrollCount = std::max(a: UnrollCount, b: RHS.UnrollCount); |
118 | |
119 | return *this; |
120 | } |
121 | |
122 | bool operator < (const OptReportLocationInfo &RHS) const { |
123 | if (Inlined < RHS.Inlined) |
124 | return true; |
125 | else if (RHS.Inlined < Inlined) |
126 | return false; |
127 | else if (Unrolled < RHS.Unrolled) |
128 | return true; |
129 | else if (RHS.Unrolled < Unrolled) |
130 | return false; |
131 | else if (Vectorized < RHS.Vectorized) |
132 | return true; |
133 | else if (RHS.Vectorized < Vectorized || Succinct) |
134 | return false; |
135 | else if (ElementCount::isKnownLT(LHS: VectorizationFactor, |
136 | RHS: RHS.VectorizationFactor)) |
137 | return true; |
138 | else if (ElementCount::isKnownGT(LHS: VectorizationFactor, |
139 | RHS: RHS.VectorizationFactor)) |
140 | return false; |
141 | else if (InterleaveCount < RHS.InterleaveCount) |
142 | return true; |
143 | else if (InterleaveCount > RHS.InterleaveCount) |
144 | return false; |
145 | else if (UnrollCount < RHS.UnrollCount) |
146 | return true; |
147 | return false; |
148 | } |
149 | }; |
150 | |
151 | typedef std::map<std::string, std::map<int, std::map<std::string, std::map<int, |
152 | OptReportLocationInfo>>>> LocationInfoTy; |
153 | } // anonymous namespace |
154 | |
155 | static bool readLocationInfo(LocationInfoTy &LocationInfo) { |
156 | ErrorOr<std::unique_ptr<MemoryBuffer>> Buf = |
157 | MemoryBuffer::getFile(Filename: InputFileName.c_str()); |
158 | if (std::error_code EC = Buf.getError()) { |
159 | WithColor::error() << "Can't open file " << InputFileName << ": " |
160 | << EC.message() << "\n" ; |
161 | return false; |
162 | } |
163 | |
164 | Expected<remarks::Format> Format = remarks::parseFormat(FormatStr: ParserFormat); |
165 | if (!Format) { |
166 | handleAllErrors(E: Format.takeError(), Handlers: [&](const ErrorInfoBase &PE) { |
167 | PE.log(OS&: WithColor::error()); |
168 | errs() << '\n'; |
169 | }); |
170 | return false; |
171 | } |
172 | |
173 | Expected<std::unique_ptr<remarks::RemarkParser>> MaybeParser = |
174 | remarks::createRemarkParserFromMeta(ParserFormat: *Format, Buf: (*Buf)->getBuffer()); |
175 | if (!MaybeParser) { |
176 | handleAllErrors(E: MaybeParser.takeError(), Handlers: [&](const ErrorInfoBase &PE) { |
177 | PE.log(OS&: WithColor::error()); |
178 | errs() << '\n'; |
179 | }); |
180 | return false; |
181 | } |
182 | remarks::RemarkParser &Parser = **MaybeParser; |
183 | |
184 | while (true) { |
185 | Expected<std::unique_ptr<remarks::Remark>> = Parser.next(); |
186 | if (!MaybeRemark) { |
187 | Error E = MaybeRemark.takeError(); |
188 | if (E.isA<remarks::EndOfFileError>()) { |
189 | // EOF. |
190 | consumeError(Err: std::move(E)); |
191 | break; |
192 | } |
193 | handleAllErrors(E: std::move(E), Handlers: [&](const ErrorInfoBase &PE) { |
194 | PE.log(OS&: WithColor::error()); |
195 | errs() << '\n'; |
196 | }); |
197 | return false; |
198 | } |
199 | |
200 | const remarks::Remark & = **MaybeRemark; |
201 | |
202 | bool Transformed = Remark.RemarkType == remarks::Type::Passed; |
203 | |
204 | ElementCount VectorizationFactor = ElementCount::getFixed(MinVal: 1); |
205 | int InterleaveCount = 1; |
206 | int UnrollCount = 1; |
207 | |
208 | for (const remarks::Argument &Arg : Remark.Args) { |
209 | if (Arg.Key == "VectorizationFactor" ) { |
210 | int MinValue = 1; |
211 | bool IsScalable = false; |
212 | if (Arg.Val.starts_with(Prefix: "vscale x " )) { |
213 | Arg.Val.drop_front(N: 9).getAsInteger(Radix: 10, Result&: MinValue); |
214 | IsScalable = true; |
215 | } else { |
216 | Arg.Val.getAsInteger(Radix: 10, Result&: MinValue); |
217 | } |
218 | VectorizationFactor = ElementCount::get(MinVal: MinValue, Scalable: IsScalable); |
219 | } else if (Arg.Key == "InterleaveCount" ) { |
220 | Arg.Val.getAsInteger(Radix: 10, Result&: InterleaveCount); |
221 | } else if (Arg.Key == "UnrollCount" ) { |
222 | Arg.Val.getAsInteger(Radix: 10, Result&: UnrollCount); |
223 | } |
224 | } |
225 | |
226 | const std::optional<remarks::RemarkLocation> &Loc = Remark.Loc; |
227 | if (!Loc) |
228 | continue; |
229 | |
230 | StringRef File = Loc->SourceFilePath; |
231 | unsigned Line = Loc->SourceLine; |
232 | unsigned Column = Loc->SourceColumn; |
233 | |
234 | // We track information on both actual and potential transformations. This |
235 | // way, if there are multiple possible things on a line that are, or could |
236 | // have been transformed, we can indicate that explicitly in the output. |
237 | auto UpdateLLII = [Transformed](OptReportLocationItemInfo &LLII) { |
238 | LLII.Analyzed = true; |
239 | if (Transformed) |
240 | LLII.Transformed = true; |
241 | }; |
242 | |
243 | if (Remark.PassName == "inline" ) { |
244 | auto &LI = LocationInfo[std::string(File)][Line] |
245 | [std::string(Remark.FunctionName)][Column]; |
246 | UpdateLLII(LI.Inlined); |
247 | } else if (Remark.PassName == "loop-unroll" ) { |
248 | auto &LI = LocationInfo[std::string(File)][Line] |
249 | [std::string(Remark.FunctionName)][Column]; |
250 | LI.UnrollCount = UnrollCount; |
251 | UpdateLLII(LI.Unrolled); |
252 | } else if (Remark.PassName == "loop-vectorize" ) { |
253 | auto &LI = LocationInfo[std::string(File)][Line] |
254 | [std::string(Remark.FunctionName)][Column]; |
255 | LI.VectorizationFactor = VectorizationFactor; |
256 | LI.InterleaveCount = InterleaveCount; |
257 | UpdateLLII(LI.Vectorized); |
258 | } |
259 | } |
260 | |
261 | return true; |
262 | } |
263 | |
264 | static bool writeReport(LocationInfoTy &LocationInfo) { |
265 | std::error_code EC; |
266 | llvm::raw_fd_ostream OS(OutputFileName, EC, llvm::sys::fs::OF_TextWithCRLF); |
267 | if (EC) { |
268 | WithColor::error() << "Can't open file " << OutputFileName << ": " |
269 | << EC.message() << "\n" ; |
270 | return false; |
271 | } |
272 | |
273 | bool FirstFile = true; |
274 | for (auto &FI : LocationInfo) { |
275 | SmallString<128> FileName(FI.first); |
276 | if (!InputRelDir.empty()) |
277 | sys::fs::make_absolute(current_directory: InputRelDir, path&: FileName); |
278 | |
279 | const auto &FileInfo = FI.second; |
280 | |
281 | ErrorOr<std::unique_ptr<MemoryBuffer>> Buf = |
282 | MemoryBuffer::getFile(Filename: FileName); |
283 | if (std::error_code EC = Buf.getError()) { |
284 | WithColor::error() << "Can't open file " << FileName << ": " |
285 | << EC.message() << "\n" ; |
286 | return false; |
287 | } |
288 | |
289 | if (FirstFile) |
290 | FirstFile = false; |
291 | else |
292 | OS << "\n" ; |
293 | |
294 | OS << "< " << FileName << "\n" ; |
295 | |
296 | // Figure out how many characters we need for the vectorization factors |
297 | // and similar. |
298 | OptReportLocationInfo MaxLI; |
299 | for (auto &FLI : FileInfo) |
300 | for (auto &FI : FLI.second) |
301 | for (auto &LI : FI.second) |
302 | MaxLI |= LI.second; |
303 | |
304 | bool NothingInlined = !MaxLI.Inlined.Transformed; |
305 | bool NothingUnrolled = !MaxLI.Unrolled.Transformed; |
306 | bool NothingVectorized = !MaxLI.Vectorized.Transformed; |
307 | |
308 | unsigned VFDigits = |
309 | llvm::utostr(X: MaxLI.VectorizationFactor.getKnownMinValue()).size(); |
310 | if (MaxLI.VectorizationFactor.isScalable()) |
311 | VFDigits += 2; // For "Nx..." |
312 | |
313 | unsigned ICDigits = llvm::utostr(X: MaxLI.InterleaveCount).size(); |
314 | unsigned UCDigits = llvm::utostr(X: MaxLI.UnrollCount).size(); |
315 | |
316 | // Figure out how many characters we need for the line numbers. |
317 | int64_t NumLines = 0; |
318 | for (line_iterator LI(*Buf.get(), false); LI != line_iterator(); ++LI) |
319 | ++NumLines; |
320 | |
321 | unsigned LNDigits = llvm::utostr(X: NumLines).size(); |
322 | |
323 | for (line_iterator LI(*Buf.get(), false); LI != line_iterator(); ++LI) { |
324 | int64_t L = LI.line_number(); |
325 | auto LII = FileInfo.find(x: L); |
326 | |
327 | auto PrintLine = [&](bool PrintFuncName, |
328 | const std::set<std::string> &FuncNameSet) { |
329 | OptReportLocationInfo LLI; |
330 | |
331 | std::map<int, OptReportLocationInfo> ColsInfo; |
332 | unsigned InlinedCols = 0, UnrolledCols = 0, VectorizedCols = 0; |
333 | |
334 | if (LII != FileInfo.end() && !FuncNameSet.empty()) { |
335 | const auto &LineInfo = LII->second; |
336 | |
337 | for (auto &CI : LineInfo.find(x: *FuncNameSet.begin())->second) { |
338 | int Col = CI.first; |
339 | ColsInfo[Col] = CI.second; |
340 | InlinedCols += CI.second.Inlined.Analyzed; |
341 | UnrolledCols += CI.second.Unrolled.Analyzed; |
342 | VectorizedCols += CI.second.Vectorized.Analyzed; |
343 | LLI |= CI.second; |
344 | } |
345 | } |
346 | |
347 | if (PrintFuncName) { |
348 | OS << " > " ; |
349 | |
350 | bool FirstFunc = true; |
351 | for (const auto &FuncName : FuncNameSet) { |
352 | if (FirstFunc) |
353 | FirstFunc = false; |
354 | else |
355 | OS << ", " ; |
356 | |
357 | bool Printed = false; |
358 | if (!NoDemangle) { |
359 | if (char *Demangled = itaniumDemangle(mangled_name: FuncName)) { |
360 | OS << Demangled; |
361 | Printed = true; |
362 | std::free(ptr: Demangled); |
363 | } |
364 | } |
365 | |
366 | if (!Printed) |
367 | OS << FuncName; |
368 | } |
369 | |
370 | OS << ":\n" ; |
371 | } |
372 | |
373 | // We try to keep the output as concise as possible. If only one thing on |
374 | // a given line could have been inlined, vectorized, etc. then we can put |
375 | // the marker on the source line itself. If there are multiple options |
376 | // then we want to distinguish them by placing the marker for each |
377 | // transformation on a separate line following the source line. When we |
378 | // do this, we use a '^' character to point to the appropriate column in |
379 | // the source line. |
380 | |
381 | std::string USpaces(Succinct ? 0 : UCDigits, ' '); |
382 | std::string VSpaces(Succinct ? 0 : VFDigits + ICDigits + 1, ' '); |
383 | |
384 | auto UStr = [UCDigits](OptReportLocationInfo &LLI) { |
385 | std::string R; |
386 | raw_string_ostream RS(R); |
387 | |
388 | if (!Succinct) { |
389 | RS << LLI.UnrollCount; |
390 | RS << std::string(UCDigits - R.size(), ' '); |
391 | } |
392 | |
393 | return R; |
394 | }; |
395 | |
396 | auto VStr = [VFDigits, |
397 | ICDigits](OptReportLocationInfo &LLI) -> std::string { |
398 | std::string R; |
399 | raw_string_ostream RS(R); |
400 | |
401 | if (!Succinct) { |
402 | if (LLI.VectorizationFactor.isScalable()) |
403 | RS << "Nx" ; |
404 | RS << LLI.VectorizationFactor.getKnownMinValue() << "," |
405 | << LLI.InterleaveCount; |
406 | RS << std::string(VFDigits + ICDigits + 1 - R.size(), ' '); |
407 | } |
408 | |
409 | return R; |
410 | }; |
411 | |
412 | OS << llvm::format_decimal(N: L, Width: LNDigits) << " " ; |
413 | OS << (LLI.Inlined.Transformed && InlinedCols < 2 ? "I" : |
414 | (NothingInlined ? "" : " " )); |
415 | OS << (LLI.Unrolled.Transformed && UnrolledCols < 2 ? |
416 | "U" + UStr(LLI) : (NothingUnrolled ? "" : " " + USpaces)); |
417 | OS << (LLI.Vectorized.Transformed && VectorizedCols < 2 ? |
418 | "V" + VStr(LLI) : (NothingVectorized ? "" : " " + VSpaces)); |
419 | |
420 | OS << " | " << *LI << "\n" ; |
421 | |
422 | for (auto &J : ColsInfo) { |
423 | if ((J.second.Inlined.Transformed && InlinedCols > 1) || |
424 | (J.second.Unrolled.Transformed && UnrolledCols > 1) || |
425 | (J.second.Vectorized.Transformed && VectorizedCols > 1)) { |
426 | OS << std::string(LNDigits + 1, ' '); |
427 | OS << (J.second.Inlined.Transformed && |
428 | InlinedCols > 1 ? "I" : (NothingInlined ? "" : " " )); |
429 | OS << (J.second.Unrolled.Transformed && |
430 | UnrolledCols > 1 ? "U" + UStr(J.second) : |
431 | (NothingUnrolled ? "" : " " + USpaces)); |
432 | OS << (J.second.Vectorized.Transformed && |
433 | VectorizedCols > 1 ? "V" + VStr(J.second) : |
434 | (NothingVectorized ? "" : " " + VSpaces)); |
435 | |
436 | OS << " | " << std::string(J.first - 1, ' ') << "^\n" ; |
437 | } |
438 | } |
439 | }; |
440 | |
441 | // We need to figure out if the optimizations for this line were the same |
442 | // in each function context. If not, then we want to group the similar |
443 | // function contexts together and display each group separately. If |
444 | // they're all the same, then we only display the line once without any |
445 | // additional markings. |
446 | std::map<std::map<int, OptReportLocationInfo>, |
447 | std::set<std::string>> UniqueLIs; |
448 | |
449 | OptReportLocationInfo AllLI; |
450 | if (LII != FileInfo.end()) { |
451 | const auto &FuncLineInfo = LII->second; |
452 | for (const auto &FLII : FuncLineInfo) { |
453 | UniqueLIs[FLII.second].insert(x: FLII.first); |
454 | |
455 | for (const auto &OI : FLII.second) |
456 | AllLI |= OI.second; |
457 | } |
458 | } |
459 | |
460 | bool NothingHappened = !AllLI.Inlined.Transformed && |
461 | !AllLI.Unrolled.Transformed && |
462 | !AllLI.Vectorized.Transformed; |
463 | if (UniqueLIs.size() > 1 && !NothingHappened) { |
464 | OS << " [[\n" ; |
465 | for (const auto &FSLI : UniqueLIs) |
466 | PrintLine(true, FSLI.second); |
467 | OS << " ]]\n" ; |
468 | } else if (UniqueLIs.size() == 1) { |
469 | PrintLine(false, UniqueLIs.begin()->second); |
470 | } else { |
471 | PrintLine(false, std::set<std::string>()); |
472 | } |
473 | } |
474 | } |
475 | |
476 | return true; |
477 | } |
478 | |
479 | int main(int argc, const char **argv) { |
480 | InitLLVM X(argc, argv); |
481 | |
482 | cl::HideUnrelatedOptions(Category&: OptReportCategory); |
483 | cl::ParseCommandLineOptions( |
484 | argc, argv, |
485 | Overview: "A tool to generate an optimization report from YAML optimization" |
486 | " record files.\n" ); |
487 | |
488 | LocationInfoTy LocationInfo; |
489 | if (!readLocationInfo(LocationInfo)) |
490 | return 1; |
491 | if (!writeReport(LocationInfo)) |
492 | return 1; |
493 | |
494 | return 0; |
495 | } |
496 | |