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