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
31using namespace llvm;
32
33// Print IR out before/after specified passes.
34static cl::list<std::string>
35 PrintBefore("print-before",
36 llvm::cl::desc("Print IR before specified passes"),
37 cl::CommaSeparated, cl::Hidden);
38
39static cl::list<std::string>
40 PrintAfter("print-after", llvm::cl::desc("Print IR after specified passes"),
41 cl::CommaSeparated, cl::Hidden);
42
43static cl::opt<bool> PrintBeforeAll("print-before-all",
44 llvm::cl::desc("Print IR before each pass"),
45 cl::init(Val: false), cl::Hidden);
46static 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.
75cl::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]
96static 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
100static 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
106static 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.
114static 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
120static 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
127static 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
134bool llvm::shouldPrintBeforeSomePass() {
135 return PrintBeforeAll || !PrintBefore.empty();
136}
137
138bool llvm::shouldPrintAfterSomePass() {
139 return PrintAfterAll || !PrintAfter.empty();
140}
141
142static bool shouldPrintBeforeOrAfterPass(StringRef PassID,
143 ArrayRef<std::string> PassesToPrint) {
144 return llvm::is_contained(Range&: PassesToPrint, Element: PassID);
145}
146
147bool llvm::shouldPrintBeforeAll() { return PrintBeforeAll; }
148
149bool llvm::shouldPrintAfterAll() { return PrintAfterAll; }
150
151bool llvm::shouldPrintBeforePass(StringRef PassID) {
152 return PrintBeforeAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintBefore);
153}
154
155bool llvm::shouldPrintAfterPass(StringRef PassID) {
156 return PrintAfterAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintAfter);
157}
158
159std::vector<std::string> llvm::printBeforePasses() {
160 return std::vector<std::string>(PrintBefore);
161}
162
163std::vector<std::string> llvm::printAfterPasses() {
164 return std::vector<std::string>(PrintAfter);
165}
166
167bool llvm::forcePrintModuleIR() { return PrintModuleScope; }
168
169bool llvm::forcePrintFuncIR() { return LoopPrintFuncScope; }
170
171bool llvm::isPassInPrintList(StringRef PassName) {
172 static const StringSet<> Set(llvm::from_range, FilterPasses);
173 return Set.empty() || Set.contains(key: PassName);
174}
175
176bool llvm::isFilterPassesEmpty() { return FilterPasses.empty(); }
177
178bool 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
184namespace {
185
186struct PrintLineRange {
187 unsigned First;
188 unsigned Last;
189};
190
191struct 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
201std::string normalizeSlashes(StringRef Path) {
202 return sys::path::convert_to_slash(path: Path, style: sys::path::Style::windows_backslash);
203}
204
205bool parseLineNumber(StringRef LineText, unsigned &Line) {
206 return !LineText.empty() && !LineText.getAsInteger(Radix: 10, Result&: Line);
207}
208
209PrintLineRange 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
226std::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
244ArrayRef<PrintSourceLocFilter> getSourceLocFilters() {
245 static const std::vector<PrintSourceLocFilter> Filters =
246 parseSourceLocFilters();
247 return Filters;
248}
249
250std::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
263bool 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
282bool 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
288bool 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
299bool 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
313bool llvm::isSourceLocFilterEmpty() { return getSourceLocFilters().empty(); }
314
315bool llvm::shouldPrintAllFunctions() {
316 return isSourceLocFilterEmpty() && isFunctionInPrintList(FunctionName: "*");
317}
318
319bool 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
334std::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
345std::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
378std::error_code llvm::cleanUpTempFiles(ArrayRef<std::string> FileName) {
379 return cleanUpTempFilesImpl(FileName, N: FileName.size());
380}
381
382std::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
424void 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