| 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 | |