1//===- OptimizerDriver.cpp - Allow BugPoint to run passes safely ----------===//
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// This file defines an interface that allows bugpoint to run various passes
10// without the threat of a buggy pass corrupting bugpoint (of course, bugpoint
11// may have its own bugs, but that's another story...). It achieves this by
12// forking a copy of itself and having the child process do the optimizations.
13// If this client dies, we can always fork a new one. :)
14//
15//===----------------------------------------------------------------------===//
16
17#include "BugDriver.h"
18#include "ToolRunner.h"
19#include "llvm/Bitcode/BitcodeWriter.h"
20#include "llvm/IR/DataLayout.h"
21#include "llvm/IR/Module.h"
22#include "llvm/Support/CommandLine.h"
23#include "llvm/Support/Debug.h"
24#include "llvm/Support/FileUtilities.h"
25#include "llvm/Support/Path.h"
26#include "llvm/Support/Program.h"
27#include "llvm/Support/ToolOutputFile.h"
28
29#define DONT_GET_PLUGIN_LOADER_OPTION
30#include "llvm/Support/PluginLoader.h"
31
32
33using namespace llvm;
34
35#define DEBUG_TYPE "bugpoint"
36
37static cl::opt<std::string>
38 OptCmd("opt-command", cl::init(Val: ""),
39 cl::desc("Path to opt. (default: search path "
40 "for 'opt'.)"));
41
42/// This writes the current "Program" to the named bitcode file. If an error
43/// occurs, true is returned.
44static bool writeProgramToFileAux(ToolOutputFile &Out, const Module &M) {
45 WriteBitcodeToFile(M, Out&: Out.os(), /* ShouldPreserveUseListOrder */ true);
46 Out.os().close();
47 if (!Out.os().has_error()) {
48 Out.keep();
49 return false;
50 }
51 return true;
52}
53
54bool BugDriver::writeProgramToFile(const std::string &Filename, int FD,
55 const Module &M) const {
56 ToolOutputFile Out(Filename, FD);
57 return writeProgramToFileAux(Out, M);
58}
59
60bool BugDriver::writeProgramToFile(int FD, const Module &M) const {
61 raw_fd_ostream OS(FD, /*shouldClose*/ false);
62 WriteBitcodeToFile(M, Out&: OS, /* ShouldPreserveUseListOrder */ true);
63 OS.flush();
64 if (!OS.has_error())
65 return false;
66 OS.clear_error();
67 return true;
68}
69
70bool BugDriver::writeProgramToFile(const std::string &Filename,
71 const Module &M) const {
72 std::error_code EC;
73 ToolOutputFile Out(Filename, EC, sys::fs::OF_None);
74 if (!EC)
75 return writeProgramToFileAux(Out, M);
76 return true;
77}
78
79/// This function is used to output the current Program to a file named
80/// "bugpoint-ID.bc".
81void BugDriver::emitProgressBitcode(const Module &M, const std::string &ID,
82 bool NoFlyer) const {
83 // Output the input to the current pass to a bitcode file, emit a message
84 // telling the user how to reproduce it: opt -foo blah.bc
85 //
86 std::string Filename = OutputPrefix + "-" + ID + ".bc";
87 if (writeProgramToFile(Filename, M)) {
88 errs() << "Error opening file '" << Filename << "' for writing!\n";
89 return;
90 }
91
92 outs() << "Emitted bitcode to '" << Filename << "'\n";
93 if (NoFlyer || PassesToRun.empty())
94 return;
95 outs() << "\n*** You can reproduce the problem with: ";
96 if (UseValgrind)
97 outs() << "valgrind ";
98 outs() << "opt " << Filename;
99 for (unsigned i = 0, e = PluginLoader::getNumPlugins(); i != e; ++i) {
100 outs() << " -load " << PluginLoader::getPlugin(num: i);
101 }
102 outs() << " " << getPassesString(Passes: PassesToRun) << "\n";
103}
104
105static cl::opt<bool> SilencePasses(
106 "silence-passes",
107 cl::desc("Suppress output of running passes (both stdout and stderr)"));
108
109static cl::list<std::string> OptArgs("opt-args", cl::Positional,
110 cl::desc("<opt arguments>..."),
111 cl::PositionalEatsArgs);
112
113/// runPasses - Run the specified passes on Program, outputting a bitcode file
114/// and writing the filename into OutputFile if successful. If the
115/// optimizations fail for some reason (optimizer crashes), return true,
116/// otherwise return false. If DeleteOutput is set to true, the bitcode is
117/// deleted on success, and the filename string is undefined. This prints to
118/// outs() a single line message indicating whether compilation was successful
119/// or failed.
120///
121bool BugDriver::runPasses(Module &Program,
122 const std::vector<std::string> &Passes,
123 std::string &OutputFilename, bool DeleteOutput,
124 bool Quiet, ArrayRef<std::string> ExtraArgs) const {
125 // setup the output file name
126 outs().flush();
127 SmallString<128> UniqueFilename;
128 std::error_code EC = sys::fs::createUniqueFile(
129 Model: OutputPrefix + "-output-%%%%%%%.bc", ResultPath&: UniqueFilename);
130 if (EC) {
131 errs() << getToolName()
132 << ": Error making unique filename: " << EC.message() << "\n";
133 return true;
134 }
135 OutputFilename = std::string(UniqueFilename);
136
137 // set up the input file name
138 Expected<sys::fs::TempFile> Temp =
139 sys::fs::TempFile::create(Model: OutputPrefix + "-input-%%%%%%%.bc");
140 if (!Temp) {
141 errs() << getToolName()
142 << ": Error making unique filename: " << toString(E: Temp.takeError())
143 << "\n";
144 return true;
145 }
146 DiscardTemp Discard{.File: *Temp};
147 raw_fd_ostream OS(Temp->FD, /*shouldClose*/ false);
148
149 WriteBitcodeToFile(M: Program, Out&: OS, /* ShouldPreserveUseListOrder */ true);
150 OS.flush();
151 if (OS.has_error()) {
152 errs() << "Error writing bitcode file: " << Temp->TmpName << "\n";
153 OS.clear_error();
154 return true;
155 }
156
157 std::string tool = OptCmd;
158 if (OptCmd.empty()) {
159 if (ErrorOr<std::string> Path =
160 FindProgramByName(ExeName: "opt", Argv0: getToolName(), MainAddr: &OutputPrefix))
161 tool = *Path;
162 else
163 errs() << Path.getError().message() << "\n";
164 }
165 if (tool.empty()) {
166 errs() << "Cannot find `opt' in PATH!\n";
167 return true;
168 }
169 if (!sys::fs::exists(Path: tool)) {
170 errs() << "Specified `opt' binary does not exist: " << tool << "\n";
171 return true;
172 }
173
174 std::string Prog;
175 if (UseValgrind) {
176 if (ErrorOr<std::string> Path = sys::findProgramByName(Name: "valgrind"))
177 Prog = *Path;
178 else
179 errs() << Path.getError().message() << "\n";
180 } else
181 Prog = tool;
182 if (Prog.empty()) {
183 errs() << "Cannot find `valgrind' in PATH!\n";
184 return true;
185 }
186
187 // setup the child process' arguments
188 SmallVector<StringRef, 8> Args;
189 if (UseValgrind) {
190 Args.push_back(Elt: "valgrind");
191 Args.push_back(Elt: "--error-exitcode=1");
192 Args.push_back(Elt: "-q");
193 Args.push_back(Elt: tool);
194 } else
195 Args.push_back(Elt: tool);
196
197 llvm::append_range(C&: Args, R&: OptArgs);
198 // Pin to legacy PM since bugpoint has lots of infra and hacks revolving
199 // around the legacy PM.
200 Args.push_back(Elt: "-bugpoint-enable-legacy-pm");
201 Args.push_back(Elt: "-disable-symbolication");
202 Args.push_back(Elt: "-o");
203 Args.push_back(Elt: OutputFilename);
204 std::vector<std::string> pass_args;
205 for (unsigned i = 0, e = PluginLoader::getNumPlugins(); i != e; ++i) {
206 pass_args.push_back(x: std::string("-load"));
207 pass_args.push_back(x: PluginLoader::getPlugin(num: i));
208 }
209 for (std::vector<std::string>::const_iterator I = Passes.begin(),
210 E = Passes.end();
211 I != E; ++I)
212 pass_args.push_back(x: std::string("-") + (*I));
213 for (std::vector<std::string>::const_iterator I = pass_args.begin(),
214 E = pass_args.end();
215 I != E; ++I)
216 Args.push_back(Elt: *I);
217 Args.push_back(Elt: Temp->TmpName);
218 Args.append(in_start: ExtraArgs.begin(), in_end: ExtraArgs.end());
219
220 LLVM_DEBUG(errs() << "\nAbout to run:\t";
221 for (unsigned i = 0, e = Args.size() - 1; i != e; ++i) errs()
222 << " " << Args[i];
223 errs() << "\n";);
224
225 std::optional<StringRef> Redirects[3] = {std::nullopt, std::nullopt,
226 std::nullopt};
227 // Redirect stdout and stderr to nowhere if SilencePasses is given.
228 if (SilencePasses) {
229 Redirects[1] = "";
230 Redirects[2] = "";
231 }
232
233 std::string ErrMsg;
234 int result = sys::ExecuteAndWait(Program: Prog, Args, Env: std::nullopt, Redirects, SecondsToWait: Timeout,
235 MemoryLimit, ErrMsg: &ErrMsg);
236
237 // If we are supposed to delete the bitcode file or if the passes crashed,
238 // remove it now. This may fail if the file was never created, but that's ok.
239 if (DeleteOutput || result != 0)
240 sys::fs::remove(path: OutputFilename);
241
242 if (!Quiet) {
243 if (result == 0)
244 outs() << "Success!\n";
245 else if (result > 0)
246 outs() << "Exited with error code '" << result << "'\n";
247 else if (result < 0) {
248 if (result == -1)
249 outs() << "Execute failed: " << ErrMsg << "\n";
250 else
251 outs() << "Crashed: " << ErrMsg << "\n";
252 }
253 if (result & 0x01000000)
254 outs() << "Dumped core\n";
255 }
256
257 // Was the child successful?
258 return result != 0;
259}
260
261std::unique_ptr<Module>
262BugDriver::runPassesOn(Module *M, const std::vector<std::string> &Passes,
263 ArrayRef<std::string> ExtraArgs) {
264 std::string BitcodeResult;
265 if (runPasses(Program&: *M, Passes, OutputFilename&: BitcodeResult, DeleteOutput: false /*delete*/, Quiet: true /*quiet*/,
266 ExtraArgs)) {
267 return nullptr;
268 }
269
270 std::unique_ptr<Module> Ret = parseInputFile(InputFilename: BitcodeResult, ctxt&: Context);
271 if (!Ret) {
272 errs() << getToolName() << ": Error reading bitcode file '" << BitcodeResult
273 << "'!\n";
274 exit(status: 1);
275 }
276 sys::fs::remove(path: BitcodeResult);
277 return Ret;
278}
279