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/StringSet.h"
12#include "llvm/ADT/Twine.h"
13#include "llvm/Support/CommandLine.h"
14#include "llvm/Support/Errc.h"
15#include "llvm/Support/ErrorHandling.h"
16#include "llvm/Support/FileSystem.h"
17#include "llvm/Support/IOSandbox.h"
18#include "llvm/Support/MemoryBuffer.h"
19#include "llvm/Support/Program.h"
20#include "llvm/Support/raw_ostream.h"
21
22using namespace llvm;
23
24// Print IR out before/after specified passes.
25static cl::list<std::string>
26 PrintBefore("print-before",
27 llvm::cl::desc("Print IR before specified passes"),
28 cl::CommaSeparated, cl::Hidden);
29
30static cl::list<std::string>
31 PrintAfter("print-after", llvm::cl::desc("Print IR after specified passes"),
32 cl::CommaSeparated, cl::Hidden);
33
34static cl::opt<bool> PrintBeforeAll("print-before-all",
35 llvm::cl::desc("Print IR before each pass"),
36 cl::init(Val: false), cl::Hidden);
37static cl::opt<bool> PrintAfterAll("print-after-all",
38 llvm::cl::desc("Print IR after each pass"),
39 cl::init(Val: false), cl::Hidden);
40
41// Print out the IR after passes, similar to -print-after-all except that it
42// only prints the IR after passes that change the IR. Those passes that do not
43// make changes to the IR are reported as not making any changes. In addition,
44// the initial IR is also reported. Other hidden options affect the output from
45// this option. -filter-passes will limit the output to the named passes that
46// actually change the IR and other passes are reported as filtered out. The
47// specified passes will either be reported as making no changes (with no IR
48// reported) or the changed IR will be reported. Also, the -filter-print-funcs
49// and -print-module-scope options will do similar filtering based on function
50// name, reporting changed IRs as functions(or modules if -print-module-scope is
51// specified) for a particular function or indicating that the IR has been
52// filtered out. The extra options can be combined, allowing only changed IRs
53// for certain passes on certain functions to be reported in different formats,
54// with the rest being reported as filtered out. The -print-before-changed
55// option will print the IR as it was before each pass that changed it. The
56// optional value of quiet will only report when the IR changes, suppressing all
57// other messages, including the initial IR. The values "diff" and "diff-quiet"
58// will present the changes in a form similar to a patch, in either verbose or
59// quiet mode, respectively. The lines that are removed and added are prefixed
60// with '-' and '+', respectively. The -filter-print-funcs and -filter-passes
61// can be used to filter the output. This reporter relies on the linux diff
62// utility to do comparisons and insert the prefixes. For systems that do not
63// have the necessary facilities, the error message will be shown in place of
64// the expected output.
65cl::opt<ChangePrinter> llvm::PrintChanged(
66 "print-changed", cl::desc("Print changed IRs"), cl::Hidden,
67 cl::ValueOptional, cl::init(Val: ChangePrinter::None),
68 cl::values(
69 clEnumValN(ChangePrinter::Quiet, "quiet", "Run in quiet mode"),
70 clEnumValN(ChangePrinter::DiffVerbose, "diff",
71 "Display patch-like changes"),
72 clEnumValN(ChangePrinter::DiffQuiet, "diff-quiet",
73 "Display patch-like changes in quiet mode"),
74 clEnumValN(ChangePrinter::ColourDiffVerbose, "cdiff",
75 "Display patch-like changes with color"),
76 clEnumValN(ChangePrinter::ColourDiffQuiet, "cdiff-quiet",
77 "Display patch-like changes in quiet mode with color"),
78 clEnumValN(ChangePrinter::DotCfgVerbose, "dot-cfg",
79 "Create a website with graphical changes"),
80 clEnumValN(ChangePrinter::DotCfgQuiet, "dot-cfg-quiet",
81 "Create a website with graphical changes in quiet mode"),
82 // Sentinel value for unspecified option.
83 clEnumValN(ChangePrinter::Verbose, "", "")));
84
85// An option for specifying the diff used by print-changed=[diff | diff-quiet]
86static cl::opt<std::string>
87 DiffBinary("print-changed-diff-path", cl::Hidden, cl::init(Val: "diff"),
88 cl::desc("system diff used by change reporters"));
89
90static cl::opt<bool>
91 PrintModuleScope("print-module-scope",
92 cl::desc("When printing IR for print-[before|after]{-all} "
93 "always print a module IR"),
94 cl::init(Val: false), cl::Hidden);
95
96static cl::opt<bool> LoopPrintFuncScope(
97 "print-loop-func-scope",
98 cl::desc("When printing IR for print-[before|after]{-all} "
99 "for a loop pass, always print function IR"),
100 cl::init(Val: false), cl::Hidden);
101
102// See the description for -print-changed for an explanation of the use
103// of this option.
104static cl::list<std::string> FilterPasses(
105 "filter-passes", cl::value_desc("pass names"),
106 cl::desc("Only consider IR changes for passes whose names "
107 "match the specified value. No-op without -print-changed"),
108 cl::CommaSeparated, cl::Hidden);
109
110static cl::list<std::string>
111 PrintFuncsList("filter-print-funcs", cl::value_desc("function names"),
112 cl::desc("Only print IR for functions whose name "
113 "match this for all print-[before|after][-all] "
114 "options"),
115 cl::CommaSeparated, cl::Hidden);
116
117/// This is a helper to determine whether to print IR before or
118/// after a pass.
119
120bool llvm::shouldPrintBeforeSomePass() {
121 return PrintBeforeAll || !PrintBefore.empty();
122}
123
124bool llvm::shouldPrintAfterSomePass() {
125 return PrintAfterAll || !PrintAfter.empty();
126}
127
128static bool shouldPrintBeforeOrAfterPass(StringRef PassID,
129 ArrayRef<std::string> PassesToPrint) {
130 return llvm::is_contained(Range&: PassesToPrint, Element: PassID);
131}
132
133bool llvm::shouldPrintBeforeAll() { return PrintBeforeAll; }
134
135bool llvm::shouldPrintAfterAll() { return PrintAfterAll; }
136
137bool llvm::shouldPrintBeforePass(StringRef PassID) {
138 return PrintBeforeAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintBefore);
139}
140
141bool llvm::shouldPrintAfterPass(StringRef PassID) {
142 return PrintAfterAll || shouldPrintBeforeOrAfterPass(PassID, PassesToPrint: PrintAfter);
143}
144
145std::vector<std::string> llvm::printBeforePasses() {
146 return std::vector<std::string>(PrintBefore);
147}
148
149std::vector<std::string> llvm::printAfterPasses() {
150 return std::vector<std::string>(PrintAfter);
151}
152
153bool llvm::forcePrintModuleIR() { return PrintModuleScope; }
154
155bool llvm::forcePrintFuncIR() { return LoopPrintFuncScope; }
156
157bool llvm::isPassInPrintList(StringRef PassName) {
158 static const StringSet<> Set(llvm::from_range, FilterPasses);
159 return Set.empty() || Set.contains(key: PassName);
160}
161
162bool llvm::isFilterPassesEmpty() { return FilterPasses.empty(); }
163
164bool llvm::isFunctionInPrintList(StringRef FunctionName) {
165 static const StringSet<> PrintFuncNames(llvm::from_range, PrintFuncsList);
166 return PrintFuncNames.empty() || PrintFuncNames.contains(key: FunctionName);
167}
168
169std::error_code cleanUpTempFilesImpl(ArrayRef<std::string> FileName,
170 unsigned N) {
171 std::error_code RC;
172 for (unsigned I = 0; I < N; ++I) {
173 std::error_code EC = sys::fs::remove(path: FileName[I]);
174 if (EC)
175 RC = EC;
176 }
177 return RC;
178}
179
180std::error_code llvm::prepareTempFiles(SmallVector<int> &FD,
181 ArrayRef<StringRef> SR,
182 SmallVector<std::string> &FileName) {
183 assert(FD.size() >= SR.size() && FileName.size() == FD.size() &&
184 "Unexpected array sizes");
185 std::error_code EC;
186 unsigned I = 0;
187 for (; I < FD.size(); ++I) {
188 if (FD[I] == -1) {
189 SmallVector<char, 200> SV;
190 EC = sys::fs::createTemporaryFile(Prefix: "tmpfile", Suffix: "txt", ResultFD&: FD[I], ResultPath&: SV);
191 if (EC)
192 break;
193 FileName[I] = Twine(SV).str();
194 }
195 if (I < SR.size()) {
196 EC = sys::fs::openFileForWrite(Name: FileName[I], ResultFD&: FD[I]);
197 if (EC)
198 break;
199 raw_fd_ostream OutStream(FD[I], /*shouldClose=*/true);
200 if (FD[I] == -1) {
201 EC = make_error_code(E: errc::io_error);
202 break;
203 }
204 OutStream << SR[I];
205 }
206 }
207 if (EC && I > 0)
208 // clean up created temporary files
209 cleanUpTempFilesImpl(FileName, N: I);
210 return EC;
211}
212
213std::error_code llvm::cleanUpTempFiles(ArrayRef<std::string> FileName) {
214 return cleanUpTempFilesImpl(FileName, N: FileName.size());
215}
216
217std::string llvm::doSystemDiff(StringRef Before, StringRef After,
218 StringRef OldLineFormat, StringRef NewLineFormat,
219 StringRef UnchangedLineFormat) {
220 auto BypassSandbox = sys::sandbox::scopedDisable();
221
222 // Store the 2 bodies into temporary files and call diff on them
223 // to get the body of the node.
224 static SmallVector<int> FD{-1, -1, -1};
225 SmallVector<StringRef> SR{Before, After};
226 static SmallVector<std::string> FileName{"", "", ""};
227 if (prepareTempFiles(FD, SR, FileName))
228 return "Unable to create temporary file.";
229
230 static ErrorOr<std::string> DiffExe = sys::findProgramByName(Name: DiffBinary);
231 if (!DiffExe)
232 return "Unable to find diff executable.";
233
234 SmallString<128> OLF, NLF, ULF;
235 ("--old-line-format=" + OldLineFormat).toVector(Out&: OLF);
236 ("--new-line-format=" + NewLineFormat).toVector(Out&: NLF);
237 ("--unchanged-line-format=" + UnchangedLineFormat).toVector(Out&: ULF);
238
239 StringRef Args[] = {DiffBinary, "-w", "-d", OLF,
240 NLF, ULF, FileName[0], FileName[1]};
241 std::optional<StringRef> Redirects[] = {std::nullopt, StringRef(FileName[2]),
242 std::nullopt};
243 int Result = sys::ExecuteAndWait(Program: *DiffExe, Args, Env: std::nullopt, Redirects);
244 if (Result < 0)
245 return "Error executing system diff.";
246 std::string Diff;
247 auto B = MemoryBuffer::getFile(Filename: FileName[2]);
248 if (B && *B)
249 Diff = (*B)->getBuffer().str();
250 else
251 return "Unable to read result.";
252
253 if (cleanUpTempFiles(FileName))
254 return "Unable to remove temporary file.";
255
256 return Diff;
257}
258
259void llvm::reportChangedIR(StringRef Before, StringRef After,
260 StringRef PassName, StringRef PassID,
261 StringRef IRName, bool IsInteresting,
262 bool ShouldReport) {
263 if (!ShouldReport && IsInteresting)
264 return;
265
266 if (IsInteresting && Before != After) {
267 errs() << ("*** IR Dump After " + PassName + " (" + PassID + ") on " +
268 IRName + " ***\n");
269 switch (PrintChanged) {
270 case ChangePrinter::None:
271 llvm_unreachable("");
272 case ChangePrinter::Quiet:
273 case ChangePrinter::Verbose:
274 case ChangePrinter::DotCfgQuiet: // unimplemented
275 case ChangePrinter::DotCfgVerbose: // unimplemented
276 errs() << After;
277 break;
278 case ChangePrinter::DiffQuiet:
279 case ChangePrinter::DiffVerbose:
280 case ChangePrinter::ColourDiffQuiet:
281 case ChangePrinter::ColourDiffVerbose: {
282 bool Color = llvm::is_contained(
283 Set: {ChangePrinter::ColourDiffQuiet, ChangePrinter::ColourDiffVerbose},
284 Element: PrintChanged.getValue());
285 StringRef Removed = Color ? "\033[31m-%l\033[0m\n" : "-%l\n";
286 StringRef Added = Color ? "\033[32m+%l\033[0m\n" : "+%l\n";
287 StringRef NoChange = " %l\n";
288 errs() << doSystemDiff(Before, After, OldLineFormat: Removed, NewLineFormat: Added, UnchangedLineFormat: NoChange);
289 break;
290 }
291 }
292 } else if (llvm::is_contained(Set: {ChangePrinter::Verbose,
293 ChangePrinter::DiffVerbose,
294 ChangePrinter::ColourDiffVerbose},
295 Element: PrintChanged.getValue())) {
296 const char *Reason =
297 IsInteresting ? " omitted because no change" : " filtered out";
298 errs() << "*** IR Dump After " << PassName;
299 if (!PassID.empty())
300 errs() << " (" << PassID << ")";
301 errs() << " on " << IRName + Reason + " ***\n";
302 }
303}
304