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