452 lines · cpp
1//===- ExecutionDriver.cpp - Allow execution of LLVM program --------------===//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-exception6//7//===----------------------------------------------------------------------===//8//9// This file contains code used to execute the program utilizing one of the10// various ways of running LLVM bitcode.11//12//===----------------------------------------------------------------------===//13 14#include "BugDriver.h"15#include "ToolRunner.h"16#include "llvm/Support/Debug.h"17#include "llvm/Support/FileUtilities.h"18#include "llvm/Support/Program.h"19#include "llvm/Support/SystemUtils.h"20#include "llvm/Support/raw_ostream.h"21#include <fstream>22 23using namespace llvm;24 25namespace {26// OutputType - Allow the user to specify the way code should be run, to test27// for miscompilation.28//29enum OutputType {30 AutoPick,31 RunLLI,32 RunJIT,33 RunLLC,34 RunLLCIA,35 CompileCustom,36 Custom37};38} // namespace39 40static cl::opt<double> AbsTolerance("abs-tolerance",41 cl::desc("Absolute error tolerated"),42 cl::init(0.0));43static cl::opt<double> RelTolerance("rel-tolerance",44 cl::desc("Relative error tolerated"),45 cl::init(0.0));46 47static cl::opt<OutputType> InterpreterSel(48 cl::desc("Specify the \"test\" i.e. suspect back-end:"),49 cl::values(clEnumValN(AutoPick, "auto", "Use best guess"),50 clEnumValN(RunLLI, "run-int", "Execute with the interpreter"),51 clEnumValN(RunJIT, "run-jit", "Execute with JIT"),52 clEnumValN(RunLLC, "run-llc", "Compile with LLC"),53 clEnumValN(RunLLCIA, "run-llc-ia",54 "Compile with LLC with integrated assembler"),55 clEnumValN(CompileCustom, "compile-custom",56 "Use -compile-command to define a command to "57 "compile the bitcode. Useful to avoid linking."),58 clEnumValN(Custom, "run-custom",59 "Use -exec-command to define a command to execute "60 "the bitcode. Useful for cross-compilation.")),61 cl::init(AutoPick));62 63static cl::opt<OutputType> SafeInterpreterSel(64 cl::desc("Specify \"safe\" i.e. known-good backend:"),65 cl::values(clEnumValN(AutoPick, "safe-auto", "Use best guess"),66 clEnumValN(RunLLC, "safe-run-llc", "Compile with LLC"),67 clEnumValN(Custom, "safe-run-custom",68 "Use -exec-command to define a command to execute "69 "the bitcode. Useful for cross-compilation.")),70 cl::init(AutoPick));71 72static cl::opt<std::string> SafeInterpreterPath(73 "safe-path", cl::desc("Specify the path to the \"safe\" backend program"),74 cl::init(""));75 76static cl::opt<bool> AppendProgramExitCode(77 "append-exit-code",78 cl::desc("Append the exit code to the output so it gets diff'd too"),79 cl::init(false));80 81static cl::opt<std::string>82 InputFile("input", cl::init("/dev/null"),83 cl::desc("Filename to pipe in as stdin (default: /dev/null)"));84 85static cl::list<std::string>86 AdditionalSOs("additional-so", cl::desc("Additional shared objects to load "87 "into executing programs"));88 89static cl::list<std::string> AdditionalLinkerArgs(90 "Xlinker", cl::desc("Additional arguments to pass to the linker"));91 92static cl::opt<std::string> CustomCompileCommand(93 "compile-command", cl::init("llc"),94 cl::desc("Command to compile the bitcode (use with -compile-custom) "95 "(default: llc)"));96 97static cl::opt<std::string> CustomExecCommand(98 "exec-command", cl::init("simulate"),99 cl::desc("Command to execute the bitcode (use with -run-custom) "100 "(default: simulate)"));101 102// Anything specified after the --args option are taken as arguments to the103// program being debugged.104cl::list<std::string> llvm::InputArgv("args", cl::Positional,105 cl::desc("<program arguments>..."),106 cl::PositionalEatsArgs);107 108cl::opt<std::string> llvm::OutputPrefix(109 "output-prefix", cl::init("bugpoint"),110 cl::desc("Prefix to use for outputs (default: 'bugpoint')"));111 112static cl::list<std::string> ToolArgv("tool-args", cl::Positional,113 cl::desc("<tool arguments>..."),114 cl::PositionalEatsArgs);115 116static cl::list<std::string> SafeToolArgv("safe-tool-args", cl::Positional,117 cl::desc("<safe-tool arguments>..."),118 cl::PositionalEatsArgs);119 120static cl::opt<std::string> CCBinary("gcc", cl::init(""),121 cl::desc("The gcc binary to use."));122 123static cl::list<std::string> CCToolArgv("gcc-tool-args", cl::Positional,124 cl::desc("<gcc-tool arguments>..."),125 cl::PositionalEatsArgs);126//===----------------------------------------------------------------------===//127// BugDriver method implementation128//129 130/// initializeExecutionEnvironment - This method is used to set up the131/// environment for executing LLVM programs.132///133Error BugDriver::initializeExecutionEnvironment() {134 outs() << "Initializing execution environment: ";135 136 // Create an instance of the AbstractInterpreter interface as specified on137 // the command line138 SafeInterpreter = nullptr;139 std::string Message;140 141 if (CCBinary.empty()) {142 if (ErrorOr<std::string> ClangPath =143 FindProgramByName("clang", getToolName(), &AbsTolerance))144 CCBinary = *ClangPath;145 else146 CCBinary = "gcc";147 }148 149 switch (InterpreterSel) {150 case AutoPick:151 if (!Interpreter) {152 InterpreterSel = RunJIT;153 Interpreter =154 AbstractInterpreter::createJIT(getToolName(), Message, &ToolArgv);155 }156 if (!Interpreter) {157 InterpreterSel = RunLLC;158 Interpreter = AbstractInterpreter::createLLC(159 getToolName(), Message, CCBinary, &ToolArgv, &CCToolArgv);160 }161 if (!Interpreter) {162 InterpreterSel = RunLLI;163 Interpreter =164 AbstractInterpreter::createLLI(getToolName(), Message, &ToolArgv);165 }166 if (!Interpreter) {167 InterpreterSel = AutoPick;168 Message = "Sorry, I can't automatically select an interpreter!\n";169 }170 break;171 case RunLLI:172 Interpreter =173 AbstractInterpreter::createLLI(getToolName(), Message, &ToolArgv);174 break;175 case RunLLC:176 case RunLLCIA:177 Interpreter = AbstractInterpreter::createLLC(178 getToolName(), Message, CCBinary, &ToolArgv, &CCToolArgv,179 InterpreterSel == RunLLCIA);180 break;181 case RunJIT:182 Interpreter =183 AbstractInterpreter::createJIT(getToolName(), Message, &ToolArgv);184 break;185 case CompileCustom:186 Interpreter = AbstractInterpreter::createCustomCompiler(187 getToolName(), Message, CustomCompileCommand);188 break;189 case Custom:190 Interpreter = AbstractInterpreter::createCustomExecutor(191 getToolName(), Message, CustomExecCommand);192 break;193 }194 if (!Interpreter)195 errs() << Message;196 else // Display informational messages on stdout instead of stderr197 outs() << Message;198 199 std::string Path = SafeInterpreterPath;200 if (Path.empty())201 Path = getToolName();202 std::vector<std::string> SafeToolArgs = SafeToolArgv;203 switch (SafeInterpreterSel) {204 case AutoPick:205 // In "llc-safe" mode, default to using LLC as the "safe" backend.206 if (InterpreterSel == RunLLC) {207 SafeInterpreterSel = RunLLC;208 SafeToolArgs.push_back("--relocation-model=pic");209 SafeInterpreter = AbstractInterpreter::createLLC(210 Path.c_str(), Message, CCBinary, &SafeToolArgs, &CCToolArgv);211 } else if (InterpreterSel != CompileCustom) {212 SafeInterpreterSel = AutoPick;213 Message = "Sorry, I can't automatically select a safe interpreter!\n";214 }215 break;216 case RunLLC:217 case RunLLCIA:218 SafeToolArgs.push_back("--relocation-model=pic");219 SafeInterpreter = AbstractInterpreter::createLLC(220 Path.c_str(), Message, CCBinary, &SafeToolArgs, &CCToolArgv,221 SafeInterpreterSel == RunLLCIA);222 break;223 case Custom:224 SafeInterpreter = AbstractInterpreter::createCustomExecutor(225 getToolName(), Message, CustomExecCommand);226 break;227 default:228 Message = "Sorry, this back-end is not supported by bugpoint as the "229 "\"safe\" backend right now!\n";230 break;231 }232 if (!SafeInterpreter && InterpreterSel != CompileCustom) {233 outs() << Message << "\nExiting.\n";234 exit(1);235 }236 237 cc = CC::create(getToolName(), Message, CCBinary, &CCToolArgv);238 if (!cc) {239 outs() << Message << "\nExiting.\n";240 exit(1);241 }242 243 // If there was an error creating the selected interpreter, quit with error.244 if (Interpreter == nullptr)245 return make_error<StringError>("Failed to init execution environment",246 inconvertibleErrorCode());247 return Error::success();248}249 250/// Try to compile the specified module, returning false and setting Error if an251/// error occurs. This is used for code generation crash testing.252Error BugDriver::compileProgram(Module &M) const {253 // Emit the program to a bitcode file...254 auto Temp =255 sys::fs::TempFile::create(OutputPrefix + "-test-program-%%%%%%%.bc");256 if (!Temp) {257 errs() << ToolName258 << ": Error making unique filename: " << toString(Temp.takeError())259 << "\n";260 exit(1);261 }262 DiscardTemp Discard{*Temp};263 if (writeProgramToFile(Temp->FD, M)) {264 errs() << ToolName << ": Error emitting bitcode to file '" << Temp->TmpName265 << "'!\n";266 exit(1);267 }268 269 // Actually compile the program!270 return Interpreter->compileProgram(Temp->TmpName, Timeout, MemoryLimit);271}272 273/// This method runs "Program", capturing the output of the program to a file,274/// returning the filename of the file. A recommended filename may be275/// optionally specified.276Expected<std::string> BugDriver::executeProgram(const Module &Program,277 std::string OutputFile,278 std::string BitcodeFile,279 const std::string &SharedObj,280 AbstractInterpreter *AI) const {281 if (!AI)282 AI = Interpreter;283 assert(AI && "Interpreter should have been created already!");284 bool CreatedBitcode = false;285 if (BitcodeFile.empty()) {286 // Emit the program to a bitcode file...287 SmallString<128> UniqueFilename;288 int UniqueFD;289 std::error_code EC = sys::fs::createUniqueFile(290 OutputPrefix + "-test-program-%%%%%%%.bc", UniqueFD, UniqueFilename);291 if (EC) {292 errs() << ToolName << ": Error making unique filename: " << EC.message()293 << "!\n";294 exit(1);295 }296 BitcodeFile = std::string(UniqueFilename);297 298 if (writeProgramToFile(BitcodeFile, UniqueFD, Program)) {299 errs() << ToolName << ": Error emitting bitcode to file '" << BitcodeFile300 << "'!\n";301 exit(1);302 }303 CreatedBitcode = true;304 }305 306 // Remove the temporary bitcode file when we are done.307 std::string BitcodePath(BitcodeFile);308 FileRemover BitcodeFileRemover(BitcodePath, CreatedBitcode && !SaveTemps);309 310 if (OutputFile.empty())311 OutputFile = OutputPrefix + "-execution-output-%%%%%%%";312 313 // Check to see if this is a valid output filename...314 SmallString<128> UniqueFile;315 std::error_code EC = sys::fs::createUniqueFile(OutputFile, UniqueFile);316 if (EC) {317 errs() << ToolName << ": Error making unique filename: " << EC.message()318 << "\n";319 exit(1);320 }321 OutputFile = std::string(UniqueFile);322 323 // Figure out which shared objects to run, if any.324 std::vector<std::string> SharedObjs(AdditionalSOs);325 if (!SharedObj.empty())326 SharedObjs.push_back(SharedObj);327 328 Expected<int> RetVal = AI->ExecuteProgram(BitcodeFile, InputArgv, InputFile,329 OutputFile, AdditionalLinkerArgs,330 SharedObjs, Timeout, MemoryLimit);331 if (Error E = RetVal.takeError())332 return std::move(E);333 334 if (*RetVal == -1) {335 errs() << "<timeout>";336 static bool FirstTimeout = true;337 if (FirstTimeout) {338 outs()339 << "\n"340 "*** Program execution timed out! This mechanism is designed to "341 "handle\n"342 " programs stuck in infinite loops gracefully. The -timeout "343 "option\n"344 " can be used to change the timeout threshold or disable it "345 "completely\n"346 " (with -timeout=0). This message is only displayed once.\n";347 FirstTimeout = false;348 }349 }350 351 if (AppendProgramExitCode) {352 std::ofstream outFile(OutputFile.c_str(), std::ios_base::app);353 outFile << "exit " << *RetVal << '\n';354 outFile.close();355 }356 357 // Return the filename we captured the output to.358 return OutputFile;359}360 361/// Used to create reference output with the "safe" backend, if reference output362/// is not provided.363Expected<std::string>364BugDriver::executeProgramSafely(const Module &Program,365 const std::string &OutputFile) const {366 return executeProgram(Program, OutputFile, "", "", SafeInterpreter);367}368 369Expected<std::string>370BugDriver::compileSharedObject(const std::string &BitcodeFile) {371 assert(Interpreter && "Interpreter should have been created already!");372 std::string OutputFile;373 374 // Using the known-good backend.375 Expected<CC::FileType> FT =376 SafeInterpreter->OutputCode(BitcodeFile, OutputFile);377 if (Error E = FT.takeError())378 return std::move(E);379 380 std::string SharedObjectFile;381 if (Error E = cc->MakeSharedObject(OutputFile, *FT, SharedObjectFile,382 AdditionalLinkerArgs))383 return std::move(E);384 385 // Remove the intermediate C file386 sys::fs::remove(OutputFile);387 388 return SharedObjectFile;389}390 391/// Calls compileProgram and then records the output into ReferenceOutputFile.392/// Returns true if reference file created, false otherwise. Note:393/// initializeExecutionEnvironment should be called BEFORE this function.394Error BugDriver::createReferenceFile(Module &M, const std::string &Filename) {395 if (Error E = compileProgram(*Program))396 return E;397 398 Expected<std::string> Result = executeProgramSafely(*Program, Filename);399 if (Error E = Result.takeError()) {400 if (Interpreter != SafeInterpreter) {401 E = joinErrors(402 std::move(E),403 make_error<StringError>(404 "*** There is a bug running the \"safe\" backend. Either"405 " debug it (for example with the -run-jit bugpoint option,"406 " if JIT is being used as the \"safe\" backend), or fix the"407 " error some other way.\n",408 inconvertibleErrorCode()));409 }410 return E;411 }412 ReferenceOutputFile = *Result;413 outs() << "\nReference output is: " << ReferenceOutputFile << "\n\n";414 return Error::success();415}416 417/// This method executes the specified module and diffs the output against the418/// file specified by ReferenceOutputFile. If the output is different, 1 is419/// returned. If there is a problem with the code generator (e.g., llc420/// crashes), this will set ErrMsg.421Expected<bool> BugDriver::diffProgram(const Module &Program,422 const std::string &BitcodeFile,423 const std::string &SharedObject,424 bool RemoveBitcode) const {425 // Execute the program, generating an output file...426 Expected<std::string> Output =427 executeProgram(Program, "", BitcodeFile, SharedObject, nullptr);428 if (Error E = Output.takeError())429 return std::move(E);430 431 std::string Error;432 bool FilesDifferent = false;433 if (int Diff = DiffFilesWithTolerance(ReferenceOutputFile, *Output,434 AbsTolerance, RelTolerance, &Error)) {435 if (Diff == 2) {436 errs() << "While diffing output: " << Error << '\n';437 exit(1);438 }439 FilesDifferent = true;440 } else {441 // Remove the generated output if there are no differences.442 sys::fs::remove(*Output);443 }444 445 // Remove the bitcode file if we are supposed to.446 if (RemoveBitcode)447 sys::fs::remove(BitcodeFile);448 return FilesDifferent;449}450 451bool BugDriver::isExecutingJIT() { return InterpreterSel == RunJIT; }452