| 1 | //===- PrintPasses.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 | #include "llvm/IR/PrintPasses.h" |
| 10 | #include "llvm/ADT/STLExtras.h" |
| 11 | #include "llvm/ADT/SmallVector.h" |
| 12 | #include "llvm/ADT/StringExtras.h" |
| 13 | #include "llvm/ADT/StringSet.h" |
| 14 | #include "llvm/ADT/Twine.h" |
| 15 | #include "llvm/IR/BasicBlock.h" |
| 16 | #include "llvm/IR/DebugInfoMetadata.h" |
| 17 | #include "llvm/IR/DebugLoc.h" |
| 18 | #include "llvm/IR/Function.h" |
| 19 | #include "llvm/IR/Instruction.h" |
| 20 | #include "llvm/Support/CommandLine.h" |
| 21 | #include "llvm/Support/Errc.h" |
| 22 | #include "llvm/Support/ErrorHandling.h" |
| 23 | #include "llvm/Support/FileSystem.h" |
| 24 | #include "llvm/Support/IOSandbox.h" |
| 25 | #include "llvm/Support/MemoryBuffer.h" |
| 26 | #include "llvm/Support/Path.h" |
| 27 | #include "llvm/Support/Program.h" |
| 28 | #include "llvm/Support/raw_ostream.h" |
| 29 | #include <vector> |
| 30 | |
| 31 | using namespace llvm; |
| 32 | |
| 33 | // Print IR out before/after specified passes. |
| 34 | static cl::list<std::string> |
| 35 | PrintBefore("print-before" , |
| 36 | llvm::cl::desc("Print IR before specified passes" ), |
| 37 | cl::CommaSeparated, cl::Hidden); |
| 38 | |
| 39 | static cl::list<std::string> |
| 40 | PrintAfter("print-after" , llvm::cl::desc("Print IR after specified passes" ), |
| 41 | cl::CommaSeparated, cl::Hidden); |
| 42 | |
| 43 | static cl::opt<bool> PrintBeforeAll("print-before-all" , |
| 44 | llvm::cl::desc("Print IR before each pass" ), |
| 45 | cl::init(Val: false), cl::Hidden); |
| 46 | static cl::opt<bool> PrintAfterAll("print-after-all" , |
| 47 | llvm::cl::desc("Print IR after each pass" ), |
| 48 | cl::init(Val: false), cl::Hidden); |
| 49 | |
| 50 | // Print out the IR after passes, similar to -print-after-all except that it |
| 51 | // only prints the IR after passes that change the IR. Those passes that do not |
| 52 | // make changes to the IR are reported as not making any changes. In addition, |
| 53 | // the initial IR is also reported. Other hidden options affect the output from |
| 54 | // this option. -filter-passes will limit the output to the named passes that |
| 55 | // actually change the IR and other passes are reported as filtered out. The |
| 56 | // specified passes will either be reported as making no changes (with no IR |
| 57 | // reported) or the changed IR will be reported. Also, the -filter-print-funcs, |
| 58 | // -filter-print-source-locs and -print-module-scope options will do similar |
| 59 | // filtering based on function name or source location, reporting changed IRs as |
| 60 | // functions(or modules if -print-module-scope is specified) for a particular |
| 61 | // function or indicating that the IR has been filtered out. The extra options |
| 62 | // can be combined, allowing only changed IRs for certain passes on certain |
| 63 | // functions or source locations to be reported in different formats, with the |
| 64 | // rest being reported as filtered out. The -print-before-changed |
| 65 | // option will print the IR as it was before each pass that changed it. The |
| 66 | // optional value of quiet will only report when the IR changes, suppressing all |
| 67 | // other messages, including the initial IR. The values "diff" and "diff-quiet" |
| 68 | // will present the changes in a form similar to a patch, in either verbose or |
| 69 | // quiet mode, respectively. The lines that are removed and added are prefixed |
| 70 | // with '-' and '+', respectively. The -filter-print-funcs, |
| 71 | // -filter-print-source-locs and -filter-passes can be used to filter the |
| 72 | // output. This reporter relies on the linux diff utility to do comparisons and |
| 73 | // insert the prefixes. For systems that do not have the necessary facilities, |
| 74 | // the error message will be shown in place of the expected output. |
| 75 | cl::opt<ChangePrinter> llvm::PrintChanged( |
| 76 | "print-changed" , cl::desc("Print changed IRs" ), cl::Hidden, |
| 77 | cl::ValueOptional, cl::init(Val: ChangePrinter::None), |
| 78 | cl::values( |
| 79 | clEnumValN(ChangePrinter::Quiet, "quiet" , "Run in quiet mode" ), |
| 80 | clEnumValN(ChangePrinter::DiffVerbose, "diff" , |
| 81 | "Display patch-like changes" ), |
| 82 | clEnumValN(ChangePrinter::DiffQuiet, "diff-quiet" , |
| 83 | "Display patch-like changes in quiet mode" ), |
| 84 | clEnumValN(ChangePrinter::ColourDiffVerbose, "cdiff" , |
| 85 | "Display patch-like changes with color" ), |
| 86 | clEnumValN(ChangePrinter::ColourDiffQuiet, "cdiff-quiet" , |
| 87 | "Display patch-like changes in quiet mode with color" ), |
| 88 | clEnumValN(ChangePrinter::DotCfgVerbose, "dot-cfg" , |
| 89 | "Create a website with graphical changes" ), |
| 90 | clEnumValN(ChangePrinter::DotCfgQuiet, "dot-cfg-quiet" , |
| 91 | "Create a website with graphical changes in quiet mode" ), |
| 92 | // Sentinel value for unspecified option. |
| 93 | clEnumValN(ChangePrinter::Verbose, "" , "" ))); |
| 94 | |
| 95 | // An option for specifying the diff used by print-changed=[diff | diff-quiet] |
| 96 | static cl::opt<std::string> |
| 97 | DiffBinary("print-changed-diff-path" , cl::Hidden, cl::init(Val: "diff" ), |
| 98 | cl::desc("system diff used by change reporters" )); |
| 99 | |
| 100 | static cl::opt<bool> |
| 101 | PrintModuleScope("print-module-scope" , |
| 102 | cl::desc("When printing IR for print-[before|after]{-all} " |
| 103 | "always print a module IR" ), |
| 104 | cl::init(Val: false), cl::Hidden); |
| 105 | |
| 106 | static cl::opt<bool> LoopPrintFuncScope( |
| 107 | "print-loop-func-scope" , |
| 108 | cl::desc("When printing IR for print-[before|after]{-all} " |
| 109 | "for a loop pass, always print function IR" ), |
| 110 | cl::init(Val: false), cl::Hidden); |
| 111 | |
| 112 | // See the description for -print-changed for an explanation of the use |
| 113 | // of this option. |
| 114 | static cl::list<std::string> FilterPasses( |
| 115 | "filter-passes" , cl::value_desc("pass names" ), |
| 116 | cl::desc("Only consider IR changes for passes whose names " |
| 117 | "match the specified value. No-op without -print-changed" ), |
| 118 | cl::CommaSeparated, cl::Hidden); |
| 119 | |
| 120 | static cl::list<std::string> |
| 121 | PrintFuncsList("filter-print-funcs" , cl::value_desc("function names" ), |
| 122 | cl::desc("Only print IR for functions whose name " |
| 123 | "match this for all print-[before|after][-all] " |
| 124 | "options" ), |
| 125 | cl::CommaSeparated, cl::Hidden); |
| 126 | |
| 127 | static cl::list<std::string> PrintSourceLocs( |
| 128 | "filter-print-source-locs" , cl::value_desc("file:line[,line-line][,line]" ), |
| 129 | cl::desc("Only print IR containing matching source locations" ), cl::Hidden); |
| 130 | |
| 131 | /// This is a helper to determine whether to print IR before or |
| 132 | /// after a pass. |
| 133 | |
| 134 | bool llvm::shouldPrintBeforeSomePass() { |
| 135 | return PrintBeforeAll || !PrintBefore.empty(); |
| 136 | } |
| 137 | |
| 138 | bool llvm::shouldPrintAfterSomePass() { |
| 139 | return PrintAfterAll || !PrintAfter.empty(); |
| 140 | } |
| 141 | |
| 142 | static bool shouldPrintBeforeOrAfterPass(StringRef PassID, |
| 143 | ArrayRef<std::string> PassesToPrint) { |
| 144 | return llvm::is_contained(Range&: PassesToPrint, Element: PassID); |
| 145 | } |
| 146 | |
| 147 | bool llvm::shouldPrintBeforeAll() { return PrintBeforeAll; } |
| 148 | |
| 149 | bool llvm::shouldPrintAfterAll() { return PrintAfterAll; } |
| 150 | |
| 151 | bool llvm::shouldPrintBeforePass(StringRef PassID) { |
| 152 | return PrintBeforeAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintBefore); |
| 153 | } |
| 154 | |
| 155 | bool llvm::shouldPrintAfterPass(StringRef PassID) { |
| 156 | return PrintAfterAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintAfter); |
| 157 | } |
| 158 | |
| 159 | std::vector<std::string> llvm::printBeforePasses() { |
| 160 | return std::vector<std::string>(PrintBefore); |
| 161 | } |
| 162 | |
| 163 | std::vector<std::string> llvm::printAfterPasses() { |
| 164 | return std::vector<std::string>(PrintAfter); |
| 165 | } |
| 166 | |
| 167 | bool llvm::forcePrintModuleIR() { return PrintModuleScope; } |
| 168 | |
| 169 | bool llvm::forcePrintFuncIR() { return LoopPrintFuncScope; } |
| 170 | |
| 171 | bool llvm::isPassInPrintList(StringRef PassName) { |
| 172 | static const StringSet<> Set(llvm::from_range, FilterPasses); |
| 173 | return Set.empty() || Set.contains(key: PassName); |
| 174 | } |
| 175 | |
| 176 | bool llvm::isFilterPassesEmpty() { return FilterPasses.empty(); } |
| 177 | |
| 178 | bool llvm::isFunctionInPrintList(StringRef FunctionName) { |
| 179 | static const StringSet<> PrintFuncNames(llvm::from_range, PrintFuncsList); |
| 180 | return PrintFuncNames.empty() || PrintFuncNames.contains(key: FunctionName) || |
| 181 | PrintFuncNames.contains(key: "*" ); |
| 182 | } |
| 183 | |
| 184 | namespace { |
| 185 | |
| 186 | struct PrintLineRange { |
| 187 | unsigned First; |
| 188 | unsigned Last; |
| 189 | }; |
| 190 | |
| 191 | struct PrintSourceLocFilter { |
| 192 | std::string File; |
| 193 | SmallVector<PrintLineRange, 4> Lines; |
| 194 | }; |
| 195 | |
| 196 | [[noreturn]] void reportBadSourceLocFilter(StringRef Filter) { |
| 197 | report_fatal_error(reason: Twine("Invalid -filter-print-source-locs value '" ) + |
| 198 | Filter + "'. Expected file:line[,line-line][,line]." ); |
| 199 | } |
| 200 | |
| 201 | std::string normalizeSlashes(StringRef Path) { |
| 202 | return sys::path::convert_to_slash(path: Path, style: sys::path::Style::windows_backslash); |
| 203 | } |
| 204 | |
| 205 | bool parseLineNumber(StringRef LineText, unsigned &Line) { |
| 206 | return !LineText.empty() && !LineText.getAsInteger(Radix: 10, Result&: Line); |
| 207 | } |
| 208 | |
| 209 | PrintLineRange parseLineRange(StringRef RangeText, StringRef FullFilter) { |
| 210 | auto [FirstText, LastText] = RangeText.split(Separator: '-'); |
| 211 | |
| 212 | unsigned First; |
| 213 | if (!parseLineNumber(LineText: FirstText, Line&: First)) |
| 214 | reportBadSourceLocFilter(Filter: FullFilter); |
| 215 | |
| 216 | if (!RangeText.contains(C: '-')) |
| 217 | return {.First: First, .Last: First}; |
| 218 | |
| 219 | unsigned Last; |
| 220 | if (!parseLineNumber(LineText: LastText, Line&: Last) || Last < First) |
| 221 | reportBadSourceLocFilter(Filter: FullFilter); |
| 222 | |
| 223 | return {.First: First, .Last: Last}; |
| 224 | } |
| 225 | |
| 226 | std::vector<PrintSourceLocFilter> parseSourceLocFilters() { |
| 227 | std::vector<PrintSourceLocFilter> Result; |
| 228 | for (const std::string &RawFilter : PrintSourceLocs) { |
| 229 | StringRef Filter(RawFilter); |
| 230 | auto [File, LineList] = Filter.rsplit(Separator: ':'); |
| 231 | if (File.empty() || LineList.empty()) |
| 232 | reportBadSourceLocFilter(Filter); |
| 233 | |
| 234 | PrintSourceLocFilter Parsed; |
| 235 | Parsed.File = normalizeSlashes(Path: File); |
| 236 | for (StringRef RangeText : llvm::split(Str: LineList, Separator: "," )) { |
| 237 | Parsed.Lines.push_back(Elt: parseLineRange(RangeText, FullFilter: Filter)); |
| 238 | } |
| 239 | Result.push_back(x: std::move(Parsed)); |
| 240 | } |
| 241 | return Result; |
| 242 | } |
| 243 | |
| 244 | ArrayRef<PrintSourceLocFilter> getSourceLocFilters() { |
| 245 | static const std::vector<PrintSourceLocFilter> Filters = |
| 246 | parseSourceLocFilters(); |
| 247 | return Filters; |
| 248 | } |
| 249 | |
| 250 | std::string makeDebugLocPath(StringRef Directory, StringRef Filename) { |
| 251 | std::string NormalizedFilename = normalizeSlashes(Path: Filename); |
| 252 | if (Directory.empty() || sys::path::is_absolute(path: NormalizedFilename)) |
| 253 | return NormalizedFilename; |
| 254 | |
| 255 | std::string NormalizedDirectory = normalizeSlashes(Path: Directory); |
| 256 | if (NormalizedDirectory.empty()) |
| 257 | return NormalizedFilename; |
| 258 | if (NormalizedDirectory.back() == '/') |
| 259 | return NormalizedDirectory + NormalizedFilename; |
| 260 | return NormalizedDirectory + "/" + NormalizedFilename; |
| 261 | } |
| 262 | |
| 263 | bool matchesFile(StringRef FilterFile, StringRef Directory, |
| 264 | StringRef Filename) { |
| 265 | std::string LocFile = normalizeSlashes(Path: Filename); |
| 266 | std::string LocPath = makeDebugLocPath(Directory, Filename); |
| 267 | |
| 268 | // Accept an exact filename or path, a basename, or a path suffix so the |
| 269 | // filter may omit leading directories. |
| 270 | if (FilterFile == LocFile || FilterFile == LocPath) |
| 271 | return true; |
| 272 | |
| 273 | StringRef LocFileRef(LocFile); |
| 274 | StringRef LocPathRef(LocPath); |
| 275 | if (sys::path::filename(path: LocFileRef) == FilterFile) |
| 276 | return true; |
| 277 | |
| 278 | std::string Suffix = (Twine("/" ) + FilterFile).str(); |
| 279 | return LocFileRef.ends_with(Suffix) || LocPathRef.ends_with(Suffix); |
| 280 | } |
| 281 | |
| 282 | bool matchesLine(ArrayRef<PrintLineRange> Ranges, unsigned Line) { |
| 283 | return any_of(Range&: Ranges, P: [Line](const PrintLineRange &Range) { |
| 284 | return Range.First <= Line && Line <= Range.Last; |
| 285 | }); |
| 286 | } |
| 287 | |
| 288 | bool matchesSourceLocFilter(const DebugLoc &Loc, |
| 289 | const PrintSourceLocFilter &Filter) { |
| 290 | auto *Scope = dyn_cast_or_null<DIScope>(Val: Loc.getScope()); |
| 291 | return Scope && |
| 292 | matchesFile(FilterFile: Filter.File, Directory: Scope->getDirectory(), |
| 293 | Filename: Scope->getFilename()) && |
| 294 | matchesLine(Ranges: Filter.Lines, Line: Loc.getLine()); |
| 295 | } |
| 296 | |
| 297 | } // namespace |
| 298 | |
| 299 | bool llvm::isSourceLocInPrintList(const DebugLoc &Loc) { |
| 300 | ArrayRef<PrintSourceLocFilter> Filters = getSourceLocFilters(); |
| 301 | if (Filters.empty()) |
| 302 | return true; |
| 303 | |
| 304 | for (DebugLoc CurLoc = Loc; CurLoc; CurLoc = CurLoc.getInlinedAt()) { |
| 305 | if (any_of(Range&: Filters, P: [&CurLoc](const PrintSourceLocFilter &Filter) { |
| 306 | return matchesSourceLocFilter(Loc: CurLoc, Filter); |
| 307 | })) |
| 308 | return true; |
| 309 | } |
| 310 | return false; |
| 311 | } |
| 312 | |
| 313 | bool llvm::isSourceLocFilterEmpty() { return getSourceLocFilters().empty(); } |
| 314 | |
| 315 | bool llvm::shouldPrintAllFunctions() { |
| 316 | return isSourceLocFilterEmpty() && isFunctionInPrintList(FunctionName: "*" ); |
| 317 | } |
| 318 | |
| 319 | bool llvm::shouldPrintFunction(const Function &F) { |
| 320 | bool SourceLocFilterEmpty = isSourceLocFilterEmpty(); |
| 321 | if (!isFunctionInPrintList(FunctionName: F.getName())) |
| 322 | return false; |
| 323 | |
| 324 | if (SourceLocFilterEmpty) |
| 325 | return true; |
| 326 | |
| 327 | for (const BasicBlock &BB : F) |
| 328 | for (const Instruction &I : BB) |
| 329 | if (isSourceLocInPrintList(Loc: I.getDebugLoc())) |
| 330 | return true; |
| 331 | return false; |
| 332 | } |
| 333 | |
| 334 | std::error_code cleanUpTempFilesImpl(ArrayRef<std::string> FileName, |
| 335 | unsigned N) { |
| 336 | std::error_code RC; |
| 337 | for (unsigned I = 0; I < N; ++I) { |
| 338 | std::error_code EC = sys::fs::remove(path: FileName[I]); |
| 339 | if (EC) |
| 340 | RC = EC; |
| 341 | } |
| 342 | return RC; |
| 343 | } |
| 344 | |
| 345 | std::error_code llvm::prepareTempFiles(SmallVector<int> &FD, |
| 346 | ArrayRef<StringRef> SR, |
| 347 | SmallVector<std::string> &FileName) { |
| 348 | assert(FD.size() >= SR.size() && FileName.size() == FD.size() && |
| 349 | "Unexpected array sizes" ); |
| 350 | std::error_code EC; |
| 351 | unsigned I = 0; |
| 352 | for (; I < FD.size(); ++I) { |
| 353 | if (FD[I] == -1) { |
| 354 | SmallVector<char, 200> SV; |
| 355 | EC = sys::fs::createTemporaryFile(Prefix: "tmpfile" , Suffix: "txt" , ResultFD&: FD[I], ResultPath&: SV); |
| 356 | if (EC) |
| 357 | break; |
| 358 | FileName[I] = Twine(SV).str(); |
| 359 | } |
| 360 | if (I < SR.size()) { |
| 361 | EC = sys::fs::openFileForWrite(Name: FileName[I], ResultFD&: FD[I]); |
| 362 | if (EC) |
| 363 | break; |
| 364 | raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true); |
| 365 | if (FD[I] == -1) { |
| 366 | EC = make_error_code(E: errc::io_error); |
| 367 | break; |
| 368 | } |
| 369 | OutStream << SR[I]; |
| 370 | } |
| 371 | } |
| 372 | if (EC && I > 0) |
| 373 | // clean up created temporary files |
| 374 | cleanUpTempFilesImpl(FileName, N: I); |
| 375 | return EC; |
| 376 | } |
| 377 | |
| 378 | std::error_code llvm::cleanUpTempFiles(ArrayRef<std::string> FileName) { |
| 379 | return cleanUpTempFilesImpl(FileName, N: FileName.size()); |
| 380 | } |
| 381 | |
| 382 | std::string llvm::doSystemDiff(StringRef Before, StringRef After, |
| 383 | StringRef OldLineFormat, StringRef NewLineFormat, |
| 384 | StringRef UnchangedLineFormat) { |
| 385 | auto BypassSandbox = sys::sandbox::scopedDisable(); |
| 386 | |
| 387 | // Store the 2 bodies into temporary files and call diff on them |
| 388 | // to get the body of the node. |
| 389 | static SmallVector<int> FD{-1, -1, -1}; |
| 390 | SmallVector<StringRef> SR{Before, After}; |
| 391 | static SmallVector<std::string> FileName{"" , "" , "" }; |
| 392 | if (prepareTempFiles(FD, SR, FileName)) |
| 393 | return "Unable to create temporary file." ; |
| 394 | |
| 395 | static ErrorOr<std::string> DiffExe = sys::findProgramByName(Name: DiffBinary); |
| 396 | if (!DiffExe) |
| 397 | return "Unable to find diff executable." ; |
| 398 | |
| 399 | SmallString<128> OLF, NLF, ULF; |
| 400 | ("--old-line-format=" + OldLineFormat).toVector(Out&: OLF); |
| 401 | ("--new-line-format=" + NewLineFormat).toVector(Out&: NLF); |
| 402 | ("--unchanged-line-format=" + UnchangedLineFormat).toVector(Out&: ULF); |
| 403 | |
| 404 | StringRef Args[] = {DiffBinary, "-w" , "-d" , OLF, |
| 405 | NLF, ULF, FileName[0], FileName[1]}; |
| 406 | std::optional<StringRef> Redirects[] = {std::nullopt, StringRef(FileName[2]), |
| 407 | std::nullopt}; |
| 408 | int Result = sys::ExecuteAndWait(Program: *DiffExe, Args, Env: std::nullopt, Redirects); |
| 409 | if (Result < 0) |
| 410 | return "Error executing system diff." ; |
| 411 | std::string Diff; |
| 412 | auto B = MemoryBuffer::getFile(Filename: FileName[2]); |
| 413 | if (B && *B) |
| 414 | Diff = (*B)->getBuffer().str(); |
| 415 | else |
| 416 | return "Unable to read result." ; |
| 417 | |
| 418 | if (cleanUpTempFiles(FileName)) |
| 419 | return "Unable to remove temporary file." ; |
| 420 | |
| 421 | return Diff; |
| 422 | } |
| 423 | |
| 424 | void llvm::reportChangedIR(StringRef Before, StringRef After, |
| 425 | StringRef PassName, StringRef PassID, |
| 426 | StringRef IRName, bool IsInteresting, |
| 427 | bool ShouldReport) { |
| 428 | if (!ShouldReport && IsInteresting) |
| 429 | return; |
| 430 | |
| 431 | if (IsInteresting && Before != After) { |
| 432 | if (After.empty() && |
| 433 | llvm::is_contained(Set: {ChangePrinter::Quiet, ChangePrinter::Verbose, |
| 434 | ChangePrinter::DotCfgQuiet, |
| 435 | ChangePrinter::DotCfgVerbose}, |
| 436 | Element: PrintChanged.getValue())) { |
| 437 | errs() << ("*** IR Deleted After " + PassName + " (" + PassID + ") on " + |
| 438 | IRName + " ***\n" ); |
| 439 | return; |
| 440 | } |
| 441 | |
| 442 | errs() << ("*** IR Dump After " + PassName + " (" + PassID + ") on " + |
| 443 | IRName + " ***\n" ); |
| 444 | switch (PrintChanged) { |
| 445 | case ChangePrinter::None: |
| 446 | llvm_unreachable("" ); |
| 447 | case ChangePrinter::Quiet: |
| 448 | case ChangePrinter::Verbose: |
| 449 | case ChangePrinter::DotCfgQuiet: // unimplemented |
| 450 | case ChangePrinter::DotCfgVerbose: // unimplemented |
| 451 | errs() << After; |
| 452 | break; |
| 453 | case ChangePrinter::DiffQuiet: |
| 454 | case ChangePrinter::DiffVerbose: |
| 455 | case ChangePrinter::ColourDiffQuiet: |
| 456 | case ChangePrinter::ColourDiffVerbose: { |
| 457 | bool Color = llvm::is_contained( |
| 458 | Set: {ChangePrinter::ColourDiffQuiet, ChangePrinter::ColourDiffVerbose}, |
| 459 | Element: PrintChanged.getValue()); |
| 460 | StringRef Removed = Color ? "\033[31m-%l\033[0m\n" : "-%l\n" ; |
| 461 | StringRef Added = Color ? "\033[32m+%l\033[0m\n" : "+%l\n" ; |
| 462 | StringRef NoChange = " %l\n" ; |
| 463 | errs() << doSystemDiff(Before, After, OldLineFormat: Removed, NewLineFormat: Added, UnchangedLineFormat: NoChange); |
| 464 | break; |
| 465 | } |
| 466 | } |
| 467 | } else if (llvm::is_contained(Set: {ChangePrinter::Verbose, |
| 468 | ChangePrinter::DiffVerbose, |
| 469 | ChangePrinter::ColourDiffVerbose}, |
| 470 | Element: PrintChanged.getValue())) { |
| 471 | const char *Reason = |
| 472 | IsInteresting ? " omitted because no change" : " filtered out" ; |
| 473 | errs() << "*** IR Dump After " << PassName; |
| 474 | if (!PassID.empty()) |
| 475 | errs() << " (" << PassID << ")" ; |
| 476 | errs() << " on " << IRName + Reason + " ***\n" ; |
| 477 | } |
| 478 | } |
| 479 | |