diff options
author | Nick Lewycky <nicholas@mxc.ca> | 2010-04-12 05:08:25 +0000 |
---|---|---|
committer | Nick Lewycky <nicholas@mxc.ca> | 2010-04-12 05:08:25 +0000 |
commit | 22ff748712b348300e51248339b6e8cf9b59e2c6 (patch) | |
tree | ab07a4e3d46ed34d8f3510ed11ea772016bca4bd | |
parent | 67a71b5306c42f1d56a0d58635432b86206c2a9a (diff) | |
download | external_llvm-22ff748712b348300e51248339b6e8cf9b59e2c6.zip external_llvm-22ff748712b348300e51248339b6e8cf9b59e2c6.tar.gz external_llvm-22ff748712b348300e51248339b6e8cf9b59e2c6.tar.bz2 |
Remove use of exceptions from bugpoint. No deliberate functionality change!
git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@101013 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | tools/bugpoint/BugDriver.cpp | 85 | ||||
-rw-r--r-- | tools/bugpoint/BugDriver.h | 51 | ||||
-rw-r--r-- | tools/bugpoint/CrashDebugger.cpp | 63 | ||||
-rw-r--r-- | tools/bugpoint/ExecutionDriver.cpp | 72 | ||||
-rw-r--r-- | tools/bugpoint/FindBugs.cpp | 35 | ||||
-rw-r--r-- | tools/bugpoint/ListReducer.h | 30 | ||||
-rw-r--r-- | tools/bugpoint/Miscompilation.cpp | 287 | ||||
-rw-r--r-- | tools/bugpoint/ToolRunner.cpp | 93 | ||||
-rw-r--r-- | tools/bugpoint/ToolRunner.h | 64 | ||||
-rw-r--r-- | tools/bugpoint/bugpoint.cpp | 24 |
10 files changed, 463 insertions, 341 deletions
diff --git a/tools/bugpoint/BugDriver.cpp b/tools/bugpoint/BugDriver.cpp index d676a43..45a0d4d 100644 --- a/tools/bugpoint/BugDriver.cpp +++ b/tools/bugpoint/BugDriver.cpp @@ -115,30 +115,25 @@ bool BugDriver::addSources(const std::vector<std::string> &Filenames) { assert(Program == 0 && "Cannot call addSources multiple times!"); assert(!Filenames.empty() && "Must specify at least on input filename!"); - try { - // Load the first input file. - Program = ParseInputFile(Filenames[0], Context); - if (Program == 0) return true; + // Load the first input file. + Program = ParseInputFile(Filenames[0], Context); + if (Program == 0) return true; - if (!run_as_child) - outs() << "Read input file : '" << Filenames[0] << "'\n"; + if (!run_as_child) + outs() << "Read input file : '" << Filenames[0] << "'\n"; - for (unsigned i = 1, e = Filenames.size(); i != e; ++i) { - std::auto_ptr<Module> M(ParseInputFile(Filenames[i], Context)); - if (M.get() == 0) return true; + for (unsigned i = 1, e = Filenames.size(); i != e; ++i) { + std::auto_ptr<Module> M(ParseInputFile(Filenames[i], Context)); + if (M.get() == 0) return true; - if (!run_as_child) - outs() << "Linking in input file: '" << Filenames[i] << "'\n"; - std::string ErrorMessage; - if (Linker::LinkModules(Program, M.get(), &ErrorMessage)) { - errs() << ToolName << ": error linking in '" << Filenames[i] << "': " - << ErrorMessage << '\n'; - return true; - } + if (!run_as_child) + outs() << "Linking in input file: '" << Filenames[i] << "'\n"; + std::string ErrorMessage; + if (Linker::LinkModules(Program, M.get(), &ErrorMessage)) { + errs() << ToolName << ": error linking in '" << Filenames[i] << "': " + << ErrorMessage << '\n'; + return true; } - } catch (const std::string &Error) { - errs() << ToolName << ": error reading input '" << Error << "'\n"; - return true; } if (!run_as_child) @@ -153,7 +148,7 @@ bool BugDriver::addSources(const std::vector<std::string> &Filenames) { /// run - The top level method that is invoked after all of the instance /// variables are set up from command line arguments. /// -bool BugDriver::run() { +bool BugDriver::run(std::string &ErrMsg) { // The first thing to do is determine if we're running as a child. If we are, // then what to do is very narrow. This form of invocation is only called // from the runPasses method to actually run those passes in a child process. @@ -165,7 +160,7 @@ bool BugDriver::run() { if (run_find_bugs) { // Rearrange the passes and apply them to the program. Repeat this process // until the user kills the program or we find a bug. - return runManyPasses(PassesToRun); + return runManyPasses(PassesToRun, ErrMsg); } // If we're not running as a child, the first thing that we must do is @@ -186,14 +181,13 @@ bool BugDriver::run() { // Test to see if we have a code generator crash. outs() << "Running the code generator to test for a crash: "; - try { - compileProgram(Program); - outs() << '\n'; - } catch (ToolExecutionError &TEE) { - outs() << TEE.what(); - return debugCodeGeneratorCrash(); + std::string Error; + compileProgram(Program, &Error); + if (!Error.empty()) { + outs() << Error; + return debugCodeGeneratorCrash(ErrMsg); } - + outs() << '\n'; // Run the raw input to see where we are coming from. If a reference output // was specified, make sure that the raw output matches it. If not, it's a @@ -202,8 +196,8 @@ bool BugDriver::run() { bool CreatedOutput = false; if (ReferenceOutputFile.empty()) { outs() << "Generating reference output from raw program: "; - if(!createReferenceFile(Program)){ - return debugCodeGeneratorCrash(); + if (!createReferenceFile(Program)) { + return debugCodeGeneratorCrash(ErrMsg); } CreatedOutput = true; } @@ -217,24 +211,29 @@ bool BugDriver::run() { // matches, then we assume there is a miscompilation bug and try to // diagnose it. outs() << "*** Checking the code generator...\n"; - try { - if (!diffProgram()) { - outs() << "\n*** Output matches: Debugging miscompilation!\n"; - return debugMiscompilation(); + bool Diff = diffProgram("", "", false, &Error); + if (!Error.empty()) { + errs() << Error; + return debugCodeGeneratorCrash(ErrMsg); + } + if (!Diff) { + outs() << "\n*** Output matches: Debugging miscompilation!\n"; + debugMiscompilation(&Error); + if (!Error.empty()) { + errs() << Error; + return debugCodeGeneratorCrash(ErrMsg); } - } catch (ToolExecutionError &TEE) { - errs() << TEE.what(); - return debugCodeGeneratorCrash(); + return false; } outs() << "\n*** Input program does not match reference diff!\n"; outs() << "Debugging code generator problem!\n"; - try { - return debugCodeGenerator(); - } catch (ToolExecutionError &TEE) { - errs() << TEE.what(); - return debugCodeGeneratorCrash(); + bool Failure = debugCodeGenerator(&Error); + if (!Error.empty()) { + errs() << Error; + return debugCodeGeneratorCrash(ErrMsg); } + return Failure; } void llvm::PrintFunctionList(const std::vector<Function*> &Funcs) { diff --git a/tools/bugpoint/BugDriver.h b/tools/bugpoint/BugDriver.h index 819cc98..c3c847f 100644 --- a/tools/bugpoint/BugDriver.h +++ b/tools/bugpoint/BugDriver.h @@ -88,7 +88,7 @@ public: /// variables are set up from command line arguments. The \p as_child argument /// indicates whether the driver is to run in parent mode or child mode. /// - bool run(); + bool run(std::string &ErrMsg); /// debugOptimizerCrash - This method is called when some optimizer pass /// crashes on input. It attempts to prune down the testcase to something @@ -99,12 +99,12 @@ public: /// debugCodeGeneratorCrash - This method is called when the code generator /// crashes on an input. It attempts to reduce the input as much as possible /// while still causing the code generator to crash. - bool debugCodeGeneratorCrash(); + bool debugCodeGeneratorCrash(std::string &Error); /// debugMiscompilation - This method is used when the passes selected are not /// crashing, but the generated output is semantically different from the /// input. - bool debugMiscompilation(); + void debugMiscompilation(std::string *Error); /// debugPassMiscompilation - This method is called when the specified pass /// miscompiles Program as input. It tries to reduce the testcase to @@ -118,12 +118,13 @@ public: /// compileSharedObject - This method creates a SharedObject from a given /// BitcodeFile for debugging a code generator. /// - std::string compileSharedObject(const std::string &BitcodeFile); + std::string compileSharedObject(const std::string &BitcodeFile, + std::string &Error); /// debugCodeGenerator - This method narrows down a module to a function or /// set of functions, using the CBE as a ``safe'' code generator for other /// functions that are not under consideration. - bool debugCodeGenerator(); + bool debugCodeGenerator(std::string *Error); /// isExecutingJIT - Returns true if bugpoint is currently testing the JIT /// @@ -164,27 +165,29 @@ public: /// the specified one as the current program. void setNewProgram(Module *M); - /// compileProgram - Try to compile the specified module, throwing an - /// exception if an error occurs, or returning normally if not. This is used - /// for code generation crash testing. + /// compileProgram - Try to compile the specified module, returning false and + /// setting Error if an error occurs. This is used for code generation + /// crash testing. /// - void compileProgram(Module *M); + void compileProgram(Module *M, std::string *Error); /// executeProgram - This method runs "Program", capturing the output of the - /// program to a file, returning the filename of the file. A recommended - /// filename may be optionally specified. If there is a problem with the code - /// generator (e.g., llc crashes), this will throw an exception. + /// program to a file. The recommended filename will be filled in with the + /// name of the file with the captured output. If there is a problem with + /// the code generator (e.g., llc crashes), this will throw an exception. /// - std::string executeProgram(std::string RequestedOutputFilename = "", - std::string Bitcode = "", - const std::string &SharedObjects = "", - AbstractInterpreter *AI = 0); + std::string executeProgram(std::string OutputFilename, + std::string Bitcode, + const std::string &SharedObjects, + AbstractInterpreter *AI, + std::string *Error); /// executeProgramSafely - Used to create reference output with the "safe" /// backend, if reference output is not provided. If there is a problem with - /// the code generator (e.g., llc crashes), this will throw an exception. + /// the code generator (e.g., llc crashes), this will return false and set + /// Error. /// - std::string executeProgramSafely(std::string OutputFile = ""); + std::string executeProgramSafely(std::string OutputFile, std::string *Error); /// createReferenceFile - calls compileProgram and then records the output /// into ReferenceOutputFile. Returns true if reference file created, false @@ -196,13 +199,14 @@ public: /// diffProgram - This method executes the specified module and diffs the /// output against the file specified by ReferenceOutputFile. If the output - /// is different, true is returned. If there is a problem with the code - /// generator (e.g., llc crashes), this will throw an exception. + /// is different, 1 is returned. If there is a problem with the code + /// generator (e.g., llc crashes), this will return -1 and set Error. /// bool diffProgram(const std::string &BitcodeFile = "", const std::string &SharedObj = "", - bool RemoveBitcode = false); - + bool RemoveBitcode = false, + std::string *Error = 0); + /// EmitProgressBitcode - This function is used to output the current Program /// to a file named "bugpoint-ID.bc". /// @@ -266,7 +270,8 @@ public: /// If the passes did not compile correctly, output the command required to /// recreate the failure. This returns true if a compiler error is found. /// - bool runManyPasses(const std::vector<const PassInfo*> &AllPasses); + bool runManyPasses(const std::vector<const PassInfo*> &AllPasses, + std::string &ErrMsg); /// writeProgramToFile - This writes the current "Program" to the named /// bitcode file. If an error occurs, true is returned. diff --git a/tools/bugpoint/CrashDebugger.cpp b/tools/bugpoint/CrashDebugger.cpp index b51bdb4..46b33d2 100644 --- a/tools/bugpoint/CrashDebugger.cpp +++ b/tools/bugpoint/CrashDebugger.cpp @@ -53,13 +53,15 @@ namespace llvm { // passes. If we return true, we update the current module of bugpoint. // virtual TestResult doTest(std::vector<const PassInfo*> &Removed, - std::vector<const PassInfo*> &Kept); + std::vector<const PassInfo*> &Kept, + std::string &Error); }; } ReducePassList::TestResult ReducePassList::doTest(std::vector<const PassInfo*> &Prefix, - std::vector<const PassInfo*> &Suffix) { + std::vector<const PassInfo*> &Suffix, + std::string &Error) { sys::Path PrefixOutput; Module *OrigProgram = 0; if (!Prefix.empty()) { @@ -107,27 +109,26 @@ namespace { bool (*TestFn)(BugDriver &, Module *); public: ReduceCrashingGlobalVariables(BugDriver &bd, - bool (*testFn)(BugDriver&, Module*)) + bool (*testFn)(BugDriver &, Module *)) : BD(bd), TestFn(testFn) {} - virtual TestResult doTest(std::vector<GlobalVariable*>& Prefix, - std::vector<GlobalVariable*>& Kept) { + virtual TestResult doTest(std::vector<GlobalVariable*> &Prefix, + std::vector<GlobalVariable*> &Kept, + std::string &Error) { if (!Kept.empty() && TestGlobalVariables(Kept)) return KeepSuffix; - if (!Prefix.empty() && TestGlobalVariables(Prefix)) return KeepPrefix; - return NoFailure; } - bool TestGlobalVariables(std::vector<GlobalVariable*>& GVs); + bool TestGlobalVariables(std::vector<GlobalVariable*> &GVs); }; } bool ReduceCrashingGlobalVariables::TestGlobalVariables( - std::vector<GlobalVariable*>& GVs) { + std::vector<GlobalVariable*> &GVs) { // Clone the program to try hacking it apart... DenseMap<const Value*, Value*> ValueMap; Module *M = CloneModule(BD.getProgram(), ValueMap); @@ -182,7 +183,8 @@ namespace llvm { : BD(bd), TestFn(testFn) {} virtual TestResult doTest(std::vector<Function*> &Prefix, - std::vector<Function*> &Kept) { + std::vector<Function*> &Kept, + std::string &Error) { if (!Kept.empty() && TestFuncs(Kept)) return KeepSuffix; if (!Prefix.empty() && TestFuncs(Prefix)) @@ -253,7 +255,8 @@ namespace { : BD(bd), TestFn(testFn) {} virtual TestResult doTest(std::vector<const BasicBlock*> &Prefix, - std::vector<const BasicBlock*> &Kept) { + std::vector<const BasicBlock*> &Kept, + std::string &Error) { if (!Kept.empty() && TestBlocks(Kept)) return KeepSuffix; if (!Prefix.empty() && TestBlocks(Prefix)) @@ -355,7 +358,8 @@ namespace { : BD(bd), TestFn(testFn) {} virtual TestResult doTest(std::vector<const Instruction*> &Prefix, - std::vector<const Instruction*> &Kept) { + std::vector<const Instruction*> &Kept, + std::string &Error) { if (!Kept.empty() && TestInsts(Kept)) return KeepSuffix; if (!Prefix.empty() && TestInsts(Prefix)) @@ -421,7 +425,8 @@ bool ReduceCrashingInstructions::TestInsts(std::vector<const Instruction*> /// DebugACrash - Given a predicate that determines whether a component crashes /// on a program, try to destructively reduce the program while still keeping /// the predicate true. -static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *)) { +static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *), + std::string &Error) { // See if we can get away with nuking some of the global variable initializers // in the program... if (!NoGlobalRM && @@ -464,7 +469,9 @@ static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *)) { << "variables in the testcase\n"; unsigned OldSize = GVs.size(); - ReduceCrashingGlobalVariables(BD, TestFn).reduceList(GVs); + ReduceCrashingGlobalVariables(BD, TestFn).reduceList(GVs, Error); + if (!Error.empty()) + return true; if (GVs.size() < OldSize) BD.EmitProgressBitcode("reduced-global-variables"); @@ -485,7 +492,7 @@ static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *)) { "in the testcase\n"; unsigned OldSize = Functions.size(); - ReduceCrashingFunctions(BD, TestFn).reduceList(Functions); + ReduceCrashingFunctions(BD, TestFn).reduceList(Functions, Error); if (Functions.size() < OldSize) BD.EmitProgressBitcode("reduced-function"); @@ -503,7 +510,7 @@ static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *)) { for (Function::const_iterator FI = I->begin(), E = I->end(); FI !=E; ++FI) Blocks.push_back(FI); unsigned OldSize = Blocks.size(); - ReduceCrashingBlocks(BD, TestFn).reduceList(Blocks); + ReduceCrashingBlocks(BD, TestFn).reduceList(Blocks, Error); if (Blocks.size() < OldSize) BD.EmitProgressBitcode("reduced-blocks"); } @@ -521,7 +528,7 @@ static bool DebugACrash(BugDriver &BD, bool (*TestFn)(BugDriver &, Module *)) { if (!isa<TerminatorInst>(I)) Insts.push_back(I); - ReduceCrashingInstructions(BD, TestFn).reduceList(Insts); + ReduceCrashingInstructions(BD, TestFn).reduceList(Insts, Error); } // FIXME: This should use the list reducer to converge faster by deleting @@ -614,9 +621,11 @@ static bool TestForOptimizerCrash(BugDriver &BD, Module *M) { bool BugDriver::debugOptimizerCrash(const std::string &ID) { outs() << "\n*** Debugging optimizer crash!\n"; + std::string Error; // Reduce the list of passes which causes the optimizer to crash... if (!BugpointIsInterrupted) - ReducePassList(*this).reduceList(PassesToRun); + ReducePassList(*this).reduceList(PassesToRun, Error); + assert(Error.empty()); outs() << "\n*** Found crashing pass" << (PassesToRun.size() == 1 ? ": " : "es: ") @@ -624,25 +633,27 @@ bool BugDriver::debugOptimizerCrash(const std::string &ID) { EmitProgressBitcode(ID); - return DebugACrash(*this, TestForOptimizerCrash); + bool Success = DebugACrash(*this, TestForOptimizerCrash, Error); + assert(Error.empty()); + return Success; } static bool TestForCodeGenCrash(BugDriver &BD, Module *M) { - try { - BD.compileProgram(M); - errs() << '\n'; - return false; - } catch (ToolExecutionError &) { + std::string Error; + BD.compileProgram(M, &Error); + if (!Error.empty()) { errs() << "<crash>\n"; return true; // Tool is still crashing. } + errs() << '\n'; + return false; } /// debugCodeGeneratorCrash - This method is called when the code generator /// crashes on an input. It attempts to reduce the input as much as possible /// while still causing the code generator to crash. -bool BugDriver::debugCodeGeneratorCrash() { +bool BugDriver::debugCodeGeneratorCrash(std::string &Error) { errs() << "*** Debugging code generator crash!\n"; - return DebugACrash(*this, TestForCodeGenCrash); + return DebugACrash(*this, TestForCodeGenCrash, Error); } diff --git a/tools/bugpoint/ExecutionDriver.cpp b/tools/bugpoint/ExecutionDriver.cpp index 3b93a1a..9eb3314 100644 --- a/tools/bugpoint/ExecutionDriver.cpp +++ b/tools/bugpoint/ExecutionDriver.cpp @@ -278,15 +278,15 @@ bool BugDriver::initializeExecutionEnvironment() { return Interpreter == 0; } -/// compileProgram - Try to compile the specified module, throwing an exception -/// if an error occurs, or returning normally if not. This is used for code -/// generation crash testing. +/// compileProgram - Try to compile the specified module, returning false and +/// setting Error if an error occurs. This is used for code generation +/// crash testing. /// -void BugDriver::compileProgram(Module *M) { +void BugDriver::compileProgram(Module *M, std::string *Error) { // Emit the program to a bitcode file... sys::Path BitcodeFile (OutputPrefix + "-test-program.bc"); std::string ErrMsg; - if (BitcodeFile.makeUnique(true,&ErrMsg)) { + if (BitcodeFile.makeUnique(true, &ErrMsg)) { errs() << ToolName << ": Error making unique filename: " << ErrMsg << "\n"; exit(1); @@ -297,11 +297,11 @@ void BugDriver::compileProgram(Module *M) { exit(1); } - // Remove the temporary bitcode file when we are done. + // Remove the temporary bitcode file when we are done. FileRemover BitcodeFileRemover(BitcodeFile, !SaveTemps); // Actually compile the program! - Interpreter->compileProgram(BitcodeFile.str()); + Interpreter->compileProgram(BitcodeFile.str(), Error); } @@ -312,7 +312,8 @@ void BugDriver::compileProgram(Module *M) { std::string BugDriver::executeProgram(std::string OutputFile, std::string BitcodeFile, const std::string &SharedObj, - AbstractInterpreter *AI) { + AbstractInterpreter *AI, + std::string *Error) { if (AI == 0) AI = Interpreter; assert(AI && "Interpreter should have been created already!"); bool CreatedBitcode = false; @@ -355,9 +356,11 @@ std::string BugDriver::executeProgram(std::string OutputFile, if (!SharedObj.empty()) SharedObjs.push_back(SharedObj); - int RetVal = AI->ExecuteProgram(BitcodeFile, InputArgv, InputFile, - OutputFile, AdditionalLinkerArgs, SharedObjs, + int RetVal = AI->ExecuteProgram(BitcodeFile, InputArgv, InputFile, OutputFile, + Error, AdditionalLinkerArgs, SharedObjs, Timeout, MemoryLimit); + if (!Error->empty()) + return OutputFile; if (RetVal == -1) { errs() << "<timeout>"; @@ -385,21 +388,28 @@ std::string BugDriver::executeProgram(std::string OutputFile, /// executeProgramSafely - Used to create reference output with the "safe" /// backend, if reference output is not provided. /// -std::string BugDriver::executeProgramSafely(std::string OutputFile) { - std::string outFN = executeProgram(OutputFile, "", "", SafeInterpreter); - return outFN; +std::string BugDriver::executeProgramSafely(std::string OutputFile, + std::string *Error) { + return executeProgram(OutputFile, "", "", SafeInterpreter, Error); } -std::string BugDriver::compileSharedObject(const std::string &BitcodeFile) { +std::string BugDriver::compileSharedObject(const std::string &BitcodeFile, + std::string &Error) { assert(Interpreter && "Interpreter should have been created already!"); sys::Path OutputFile; // Using the known-good backend. - GCC::FileType FT = SafeInterpreter->OutputCode(BitcodeFile, OutputFile); + GCC::FileType FT = SafeInterpreter->OutputCode(BitcodeFile, OutputFile, + Error); + if (!Error.empty()) + return ""; std::string SharedObjectFile; - if (gcc->MakeSharedObject(OutputFile.str(), FT, - SharedObjectFile, AdditionalLinkerArgs)) + bool Failure = gcc->MakeSharedObject(OutputFile.str(), FT, SharedObjectFile, + AdditionalLinkerArgs, Error); + if (!Error.empty()) + return ""; + if (Failure) exit(1); // Remove the intermediate C file @@ -414,16 +424,14 @@ std::string BugDriver::compileSharedObject(const std::string &BitcodeFile) { /// this function. /// bool BugDriver::createReferenceFile(Module *M, const std::string &Filename) { - try { - compileProgram(Program); - } catch (ToolExecutionError &) { + std::string Error; + compileProgram(Program, &Error); + if (!Error.empty()) return false; - } - try { - ReferenceOutputFile = executeProgramSafely(Filename); - outs() << "\nReference output is: " << ReferenceOutputFile << "\n\n"; - } catch (ToolExecutionError &TEE) { - errs() << TEE.what(); + + ReferenceOutputFile = executeProgramSafely(Filename, &Error); + if (!Error.empty()) { + errs() << Error; if (Interpreter != SafeInterpreter) { errs() << "*** There is a bug running the \"safe\" backend. Either" << " debug it (for example with the -run-cbe bugpoint option," @@ -432,19 +440,23 @@ bool BugDriver::createReferenceFile(Module *M, const std::string &Filename) { } return false; } + outs() << "\nReference output is: " << ReferenceOutputFile << "\n\n"; return true; } /// diffProgram - This method executes the specified module and diffs the /// output against the file specified by ReferenceOutputFile. If the output -/// is different, true is returned. If there is a problem with the code -/// generator (e.g., llc crashes), this will throw an exception. +/// is different, 1 is returned. If there is a problem with the code +/// generator (e.g., llc crashes), this will return -1 and set Error. /// bool BugDriver::diffProgram(const std::string &BitcodeFile, const std::string &SharedObject, - bool RemoveBitcode) { + bool RemoveBitcode, + std::string *ErrMsg) { // Execute the program, generating an output file... - sys::Path Output(executeProgram("", BitcodeFile, SharedObject, 0)); + sys::Path Output(executeProgram("", BitcodeFile, SharedObject, 0, ErrMsg)); + if (!ErrMsg->empty()) + return false; std::string Error; bool FilesDifferent = false; diff --git a/tools/bugpoint/FindBugs.cpp b/tools/bugpoint/FindBugs.cpp index b27a25c..224c717 100644 --- a/tools/bugpoint/FindBugs.cpp +++ b/tools/bugpoint/FindBugs.cpp @@ -29,7 +29,8 @@ using namespace llvm; /// If the passes did not compile correctly, output the command required to /// recreate the failure. This returns true if a compiler error is found. /// -bool BugDriver::runManyPasses(const std::vector<const PassInfo*> &AllPasses) { +bool BugDriver::runManyPasses(const std::vector<const PassInfo*> &AllPasses, + std::string &ErrMsg) { setPassesToRun(AllPasses); outs() << "Starting bug finding procedure...\n\n"; @@ -74,33 +75,33 @@ bool BugDriver::runManyPasses(const std::vector<const PassInfo*> &AllPasses) { // Step 3: Compile the optimized code. // outs() << "Running the code generator to test for a crash: "; - try { - compileProgram(Program); - outs() << '\n'; - } catch (ToolExecutionError &TEE) { + std::string Error; + compileProgram(Program, &Error); + if (!Error.empty()) { outs() << "\n*** compileProgram threw an exception: "; - outs() << TEE.what(); - return debugCodeGeneratorCrash(); + outs() << Error; + return debugCodeGeneratorCrash(ErrMsg); } + outs() << '\n'; // // Step 4: Run the program and compare its output to the reference // output (created above). // outs() << "*** Checking if passes caused miscompliation:\n"; - try { - if (diffProgram(Filename, "", false)) { - outs() << "\n*** diffProgram returned true!\n"; - debugMiscompilation(); + bool Diff = diffProgram(Filename, "", false, &Error); + if (Error.empty() && Diff) { + outs() << "\n*** diffProgram returned true!\n"; + debugMiscompilation(&Error); + if (Error.empty()) return true; - } else { - outs() << "\n*** diff'd output matches!\n"; - } - } catch (ToolExecutionError &TEE) { - errs() << TEE.what(); - debugCodeGeneratorCrash(); + } + if (!Error.empty()) { + errs() << Error; + debugCodeGeneratorCrash(ErrMsg); return true; } + outs() << "\n*** diff'd output matches!\n"; sys::Path(Filename).eraseFromDisk(); diff --git a/tools/bugpoint/ListReducer.h b/tools/bugpoint/ListReducer.h index 8036d1f..5e9cff0 100644 --- a/tools/bugpoint/ListReducer.h +++ b/tools/bugpoint/ListReducer.h @@ -16,6 +16,7 @@ #define BUGPOINT_LIST_REDUCER_H #include "llvm/Support/raw_ostream.h" +#include "llvm/Support/ErrorHandling.h" #include <vector> #include <cstdlib> #include <algorithm> @@ -29,7 +30,8 @@ struct ListReducer { enum TestResult { NoFailure, // No failure of the predicate was detected KeepSuffix, // The suffix alone satisfies the predicate - KeepPrefix // The prefix alone satisfies the predicate + KeepPrefix, // The prefix alone satisfies the predicate + InternalError // Encountered an error trying to run the predicate }; virtual ~ListReducer() {} @@ -40,16 +42,17 @@ struct ListReducer { // the prefix anyway, it can. // virtual TestResult doTest(std::vector<ElTy> &Prefix, - std::vector<ElTy> &Kept) = 0; + std::vector<ElTy> &Kept, + std::string &Error) = 0; // reduceList - This function attempts to reduce the length of the specified // list while still maintaining the "test" property. This is the core of the // "work" that bugpoint does. // - bool reduceList(std::vector<ElTy> &TheList) { + bool reduceList(std::vector<ElTy> &TheList, std::string &Error) { std::vector<ElTy> empty; std::srand(0x6e5ea738); // Seed the random number generator - switch (doTest(TheList, empty)) { + switch (doTest(TheList, empty, Error)) { case KeepPrefix: if (TheList.size() == 1) // we are done, it's the base case and it fails return true; @@ -58,11 +61,15 @@ struct ListReducer { case KeepSuffix: // cannot be reached! - errs() << "bugpoint ListReducer internal error: selected empty set.\n"; - abort(); + llvm_unreachable("bugpoint ListReducer internal error: " + "selected empty set."); case NoFailure: return false; // there is no failure with the full set of passes/funcs! + + case InternalError: + assert(!Error.empty()); + return true; } // Maximal number of allowed splitting iterations, @@ -90,7 +97,7 @@ Backjump: std::random_shuffle(ShuffledList.begin(), ShuffledList.end()); errs() << "\n\n*** Testing shuffled set...\n\n"; // Check that random shuffle doesn't loose the bug - if (doTest(ShuffledList, empty) == KeepPrefix) { + if (doTest(ShuffledList, empty, Error) == KeepPrefix) { // If the bug is still here, use the shuffled list. TheList.swap(ShuffledList); MidTop = TheList.size(); @@ -109,7 +116,7 @@ Backjump: std::vector<ElTy> Prefix(TheList.begin(), TheList.begin()+Mid); std::vector<ElTy> Suffix(TheList.begin()+Mid, TheList.end()); - switch (doTest(Prefix, Suffix)) { + switch (doTest(Prefix, Suffix, Error)) { case KeepSuffix: // The property still holds. We can just drop the prefix elements, and // shorten the list to the "kept" elements. @@ -133,7 +140,10 @@ Backjump: MidTop = Mid; NumOfIterationsWithoutProgress++; break; + case InternalError: + return true; // Error was set by doTest. } + assert(Error.empty() && "doTest did not return InternalError for error"); } // Probability of backjumping from the trimming loop back to the binary @@ -167,12 +177,14 @@ Backjump: std::vector<ElTy> TestList(TheList); TestList.erase(TestList.begin()+i); - if (doTest(EmptyList, TestList) == KeepSuffix) { + if (doTest(EmptyList, TestList, Error) == KeepSuffix) { // We can trim down the list! TheList.swap(TestList); --i; // Don't skip an element of the list Changed = true; } + if (!Error.empty()) + return true; } // This can take a long time if left uncontrolled. For now, don't // iterate. diff --git a/tools/bugpoint/Miscompilation.cpp b/tools/bugpoint/Miscompilation.cpp index c2b002f..96aab95 100644 --- a/tools/bugpoint/Miscompilation.cpp +++ b/tools/bugpoint/Miscompilation.cpp @@ -49,7 +49,8 @@ namespace { ReduceMiscompilingPasses(BugDriver &bd) : BD(bd) {} virtual TestResult doTest(std::vector<const PassInfo*> &Prefix, - std::vector<const PassInfo*> &Suffix); + std::vector<const PassInfo*> &Suffix, + std::string &Error); }; } @@ -58,7 +59,8 @@ namespace { /// ReduceMiscompilingPasses::TestResult ReduceMiscompilingPasses::doTest(std::vector<const PassInfo*> &Prefix, - std::vector<const PassInfo*> &Suffix) { + std::vector<const PassInfo*> &Suffix, + std::string &Error) { // First, run the program with just the Suffix passes. If it is still broken // with JUST the kept passes, discard the prefix passes. outs() << "Checking to see if '" << getPassesString(Suffix) @@ -74,7 +76,11 @@ ReduceMiscompilingPasses::doTest(std::vector<const PassInfo*> &Prefix, } // Check to see if the finished program matches the reference output... - if (BD.diffProgram(BitcodeResult, "", true /*delete bitcode*/)) { + bool Diff = BD.diffProgram(BitcodeResult, "", true /*delete bitcode*/, + &Error); + if (!Error.empty()) + return InternalError; + if (Diff) { outs() << " nope.\n"; if (Suffix.empty()) { errs() << BD.getToolName() << ": I'm confused: the test fails when " @@ -107,7 +113,10 @@ ReduceMiscompilingPasses::doTest(std::vector<const PassInfo*> &Prefix, } // If the prefix maintains the predicate by itself, only keep the prefix! - if (BD.diffProgram(BitcodeResult)) { + Diff = BD.diffProgram(BitcodeResult, "", false, &Error); + if (!Error.empty()) + return InternalError; + if (Diff) { outs() << " nope.\n"; sys::Path(BitcodeResult).eraseFromDisk(); return KeepPrefix; @@ -143,7 +152,10 @@ ReduceMiscompilingPasses::doTest(std::vector<const PassInfo*> &Prefix, } // Run the result... - if (BD.diffProgram(BitcodeResult, "", true/*delete bitcode*/)) { + Diff = BD.diffProgram(BitcodeResult, "", true /*delete bitcode*/, &Error); + if (!Error.empty()) + return InternalError; + if (Diff) { outs() << " nope.\n"; delete OriginalInput; // We pruned down the original input... return KeepSuffix; @@ -158,22 +170,34 @@ ReduceMiscompilingPasses::doTest(std::vector<const PassInfo*> &Prefix, namespace { class ReduceMiscompilingFunctions : public ListReducer<Function*> { BugDriver &BD; - bool (*TestFn)(BugDriver &, Module *, Module *); + bool (*TestFn)(BugDriver &, Module *, Module *, std::string &); public: ReduceMiscompilingFunctions(BugDriver &bd, - bool (*F)(BugDriver &, Module *, Module *)) + bool (*F)(BugDriver &, Module *, Module *, + std::string &)) : BD(bd), TestFn(F) {} virtual TestResult doTest(std::vector<Function*> &Prefix, - std::vector<Function*> &Suffix) { - if (!Suffix.empty() && TestFuncs(Suffix)) - return KeepSuffix; - if (!Prefix.empty() && TestFuncs(Prefix)) - return KeepPrefix; + std::vector<Function*> &Suffix, + std::string &Error) { + if (!Suffix.empty()) { + bool Ret = TestFuncs(Suffix, Error); + if (!Error.empty()) + return InternalError; + if (Ret) + return KeepSuffix; + } + if (!Prefix.empty()) { + bool Ret = TestFuncs(Prefix, Error); + if (!Error.empty()) + return InternalError; + if (Ret) + return KeepPrefix; + } return NoFailure; } - bool TestFuncs(const std::vector<Function*> &Prefix); + int TestFuncs(const std::vector<Function*> &Prefix, std::string &Error); }; } @@ -184,7 +208,7 @@ namespace { /// returns. /// static bool TestMergedProgram(BugDriver &BD, Module *M1, Module *M2, - bool DeleteInputs) { + bool DeleteInputs, std::string &Error) { // Link the two portions of the program back to together. std::string ErrorMsg; if (!DeleteInputs) { @@ -202,11 +226,12 @@ static bool TestMergedProgram(BugDriver &BD, Module *M1, Module *M2, // Execute the program. If it does not match the expected output, we must // return true. - bool Broken = BD.diffProgram(); - - // Delete the linked module & restore the original - BD.swapProgramIn(OldProgram); - delete M1; + bool Broken = BD.diffProgram("", "", false, &Error); + if (!Error.empty()) { + // Delete the linked module & restore the original + BD.swapProgramIn(OldProgram); + delete M1; + } return Broken; } @@ -214,7 +239,8 @@ static bool TestMergedProgram(BugDriver &BD, Module *M1, Module *M2, /// under consideration for miscompilation vs. those that are not, and test /// accordingly. Each group of functions becomes a separate Module. /// -bool ReduceMiscompilingFunctions::TestFuncs(const std::vector<Function*>&Funcs){ +int ReduceMiscompilingFunctions::TestFuncs(const std::vector<Function*> &Funcs, + std::string &Error) { // Test to see if the function is misoptimized if we ONLY run it on the // functions listed in Funcs. outs() << "Checking to see if the program is misoptimized when " @@ -231,7 +257,7 @@ bool ReduceMiscompilingFunctions::TestFuncs(const std::vector<Function*>&Funcs){ ValueMap); // Run the predicate, note that the predicate will delete both input modules. - return TestFn(BD, ToOptimize, ToNotOptimize); + return TestFn(BD, ToOptimize, ToNotOptimize, Error); } /// DisambiguateGlobalSymbols - Give anonymous global values names. @@ -251,8 +277,10 @@ static void DisambiguateGlobalSymbols(Module *M) { /// bug. If so, it reduces the amount of code identified. /// static bool ExtractLoops(BugDriver &BD, - bool (*TestFn)(BugDriver &, Module *, Module *), - std::vector<Function*> &MiscompiledFunctions) { + bool (*TestFn)(BugDriver &, Module *, Module *, + std::string &), + std::vector<Function*> &MiscompiledFunctions, + std::string &Error) { bool MadeChange = false; while (1) { if (BugpointIsInterrupted) return MadeChange; @@ -279,7 +307,11 @@ static bool ExtractLoops(BugDriver &BD, // has broken. If something broke, then we'll inform the user and stop // extraction. AbstractInterpreter *AI = BD.switchToSafeInterpreter(); - if (TestMergedProgram(BD, ToOptimizeLoopExtracted, ToNotOptimize, false)) { + bool Failure = TestMergedProgram(BD, ToOptimizeLoopExtracted, ToNotOptimize, + false, Error); + if (!Error.empty()) + return false; + if (Failure) { BD.switchToInterpreter(AI); // Merged program doesn't work anymore! @@ -308,7 +340,10 @@ static bool ExtractLoops(BugDriver &BD, // Clone modules, the tester function will free them. Module *TOLEBackup = CloneModule(ToOptimizeLoopExtracted); Module *TNOBackup = CloneModule(ToNotOptimize); - if (!TestFn(BD, ToOptimizeLoopExtracted, ToNotOptimize)) { + Failure = TestFn(BD, ToOptimizeLoopExtracted, ToNotOptimize, Error); + if (!Error.empty()) + return false; + if (!Failure) { outs() << "*** Loop extraction masked the problem. Undoing.\n"; // If the program is not still broken, then loop extraction did something // that masked the error. Stop loop extraction now. @@ -361,31 +396,44 @@ static bool ExtractLoops(BugDriver &BD, namespace { class ReduceMiscompiledBlocks : public ListReducer<BasicBlock*> { BugDriver &BD; - bool (*TestFn)(BugDriver &, Module *, Module *); + bool (*TestFn)(BugDriver &, Module *, Module *, std::string &); std::vector<Function*> FunctionsBeingTested; public: ReduceMiscompiledBlocks(BugDriver &bd, - bool (*F)(BugDriver &, Module *, Module *), + bool (*F)(BugDriver &, Module *, Module *, + std::string &), const std::vector<Function*> &Fns) : BD(bd), TestFn(F), FunctionsBeingTested(Fns) {} virtual TestResult doTest(std::vector<BasicBlock*> &Prefix, - std::vector<BasicBlock*> &Suffix) { - if (!Suffix.empty() && TestFuncs(Suffix)) - return KeepSuffix; - if (TestFuncs(Prefix)) - return KeepPrefix; + std::vector<BasicBlock*> &Suffix, + std::string &Error) { + if (!Suffix.empty()) { + bool Ret = TestFuncs(Suffix, Error); + if (!Error.empty()) + return InternalError; + if (Ret) + return KeepSuffix; + } + if (!Prefix.empty()) { + bool Ret = TestFuncs(Prefix, Error); + if (!Error.empty()) + return InternalError; + if (Ret) + return KeepPrefix; + } return NoFailure; } - bool TestFuncs(const std::vector<BasicBlock*> &Prefix); + bool TestFuncs(const std::vector<BasicBlock*> &BBs, std::string &Error); }; } /// TestFuncs - Extract all blocks for the miscompiled functions except for the /// specified blocks. If the problem still exists, return true. /// -bool ReduceMiscompiledBlocks::TestFuncs(const std::vector<BasicBlock*> &BBs) { +bool ReduceMiscompiledBlocks::TestFuncs(const std::vector<BasicBlock*> &BBs, + std::string &Error) { // Test to see if the function is misoptimized if we ONLY run it on the // functions listed in Funcs. outs() << "Checking to see if the program is misoptimized when all "; @@ -411,7 +459,7 @@ bool ReduceMiscompiledBlocks::TestFuncs(const std::vector<BasicBlock*> &BBs) { if (Module *New = BD.ExtractMappedBlocksFromModule(BBs, ToOptimize)) { delete ToOptimize; // Run the predicate, not that the predicate will delete both input modules. - return TestFn(BD, New, ToNotOptimize); + return TestFn(BD, New, ToNotOptimize, Error); } delete ToOptimize; delete ToNotOptimize; @@ -424,8 +472,10 @@ bool ReduceMiscompiledBlocks::TestFuncs(const std::vector<BasicBlock*> &BBs) { /// the bug. /// static bool ExtractBlocks(BugDriver &BD, - bool (*TestFn)(BugDriver &, Module *, Module *), - std::vector<Function*> &MiscompiledFunctions) { + bool (*TestFn)(BugDriver &, Module *, Module *, + std::string &), + std::vector<Function*> &MiscompiledFunctions, + std::string &Error) { if (BugpointIsInterrupted) return false; std::vector<BasicBlock*> Blocks; @@ -440,11 +490,17 @@ static bool ExtractBlocks(BugDriver &BD, unsigned OldSize = Blocks.size(); // Check to see if all blocks are extractible first. - if (ReduceMiscompiledBlocks(BD, TestFn, - MiscompiledFunctions).TestFuncs(std::vector<BasicBlock*>())) { + bool Ret = ReduceMiscompiledBlocks(BD, TestFn, MiscompiledFunctions) + .TestFuncs(std::vector<BasicBlock*>(), Error); + if (!Error.empty()) + return false; + if (Ret) { Blocks.clear(); } else { - ReduceMiscompiledBlocks(BD, TestFn,MiscompiledFunctions).reduceList(Blocks); + ReduceMiscompiledBlocks(BD, TestFn, + MiscompiledFunctions).reduceList(Blocks, Error); + if (!Error.empty()) + return false; if (Blocks.size() == OldSize) return false; } @@ -505,7 +561,9 @@ static bool ExtractBlocks(BugDriver &BD, /// static std::vector<Function*> DebugAMiscompilation(BugDriver &BD, - bool (*TestFn)(BugDriver &, Module *, Module *)) { + bool (*TestFn)(BugDriver &, Module *, Module *, + std::string &), + std::string &Error) { // Okay, now that we have reduced the list of passes which are causing the // failure, see if we can pin down which functions are being // miscompiled... first build a list of all of the non-external functions in @@ -518,7 +576,10 @@ DebugAMiscompilation(BugDriver &BD, // Do the reduction... if (!BugpointIsInterrupted) - ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions); + ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions, + Error); + if (!Error.empty()) + return MiscompiledFunctions; outs() << "\n*** The following function" << (MiscompiledFunctions.size() == 1 ? " is" : "s are") @@ -529,37 +590,51 @@ DebugAMiscompilation(BugDriver &BD, // See if we can rip any loops out of the miscompiled functions and still // trigger the problem. - if (!BugpointIsInterrupted && !DisableLoopExtraction && - ExtractLoops(BD, TestFn, MiscompiledFunctions)) { - // Okay, we extracted some loops and the problem still appears. See if we - // can eliminate some of the created functions from being candidates. - DisambiguateGlobalSymbols(BD.getProgram()); - - // Do the reduction... - if (!BugpointIsInterrupted) - ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions); - - outs() << "\n*** The following function" - << (MiscompiledFunctions.size() == 1 ? " is" : "s are") - << " being miscompiled: "; - PrintFunctionList(MiscompiledFunctions); - outs() << '\n'; + if (!BugpointIsInterrupted && !DisableLoopExtraction) { + bool Ret = ExtractLoops(BD, TestFn, MiscompiledFunctions, Error); + if (!Error.empty()) + return MiscompiledFunctions; + if (Ret) { + // Okay, we extracted some loops and the problem still appears. See if + // we can eliminate some of the created functions from being candidates. + DisambiguateGlobalSymbols(BD.getProgram()); + + // Do the reduction... + if (!BugpointIsInterrupted) + ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions, + Error); + if (!Error.empty()) + return MiscompiledFunctions; + + outs() << "\n*** The following function" + << (MiscompiledFunctions.size() == 1 ? " is" : "s are") + << " being miscompiled: "; + PrintFunctionList(MiscompiledFunctions); + outs() << '\n'; + } } - if (!BugpointIsInterrupted && !DisableBlockExtraction && - ExtractBlocks(BD, TestFn, MiscompiledFunctions)) { - // Okay, we extracted some blocks and the problem still appears. See if we - // can eliminate some of the created functions from being candidates. - DisambiguateGlobalSymbols(BD.getProgram()); - - // Do the reduction... - ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions); - - outs() << "\n*** The following function" - << (MiscompiledFunctions.size() == 1 ? " is" : "s are") - << " being miscompiled: "; - PrintFunctionList(MiscompiledFunctions); - outs() << '\n'; + if (!BugpointIsInterrupted && !DisableBlockExtraction) { + bool Ret = ExtractBlocks(BD, TestFn, MiscompiledFunctions, Error); + if (!Error.empty()) + return MiscompiledFunctions; + if (Ret) { + // Okay, we extracted some blocks and the problem still appears. See if + // we can eliminate some of the created functions from being candidates. + DisambiguateGlobalSymbols(BD.getProgram()); + + // Do the reduction... + ReduceMiscompilingFunctions(BD, TestFn).reduceList(MiscompiledFunctions, + Error); + if (!Error.empty()) + return MiscompiledFunctions; + + outs() << "\n*** The following function" + << (MiscompiledFunctions.size() == 1 ? " is" : "s are") + << " being miscompiled: "; + PrintFunctionList(MiscompiledFunctions); + outs() << '\n'; + } } return MiscompiledFunctions; @@ -569,7 +644,8 @@ DebugAMiscompilation(BugDriver &BD, /// "Test" portion of the program is misoptimized. If so, return true. In any /// case, both module arguments are deleted. /// -static bool TestOptimizer(BugDriver &BD, Module *Test, Module *Safe) { +static bool TestOptimizer(BugDriver &BD, Module *Test, Module *Safe, + std::string &Error) { // Run the optimization passes on ToOptimize, producing a transformed version // of the functions being tested. outs() << " Optimizing functions being tested: "; @@ -579,8 +655,8 @@ static bool TestOptimizer(BugDriver &BD, Module *Test, Module *Safe) { delete Test; outs() << " Checking to see if the merged program executes correctly: "; - bool Broken = TestMergedProgram(BD, Optimized, Safe, true); - outs() << (Broken ? " nope.\n" : " yup.\n"); + bool Broken = TestMergedProgram(BD, Optimized, Safe, true, Error); + if (Error.empty()) outs() << (Broken ? " nope.\n" : " yup.\n"); return Broken; } @@ -589,13 +665,14 @@ static bool TestOptimizer(BugDriver &BD, Module *Test, Module *Safe) { /// crashing, but the generated output is semantically different from the /// input. /// -bool BugDriver::debugMiscompilation() { +void BugDriver::debugMiscompilation(std::string *Error) { // Make sure something was miscompiled... if (!BugpointIsInterrupted) - if (!ReduceMiscompilingPasses(*this).reduceList(PassesToRun)) { - errs() << "*** Optimized program matches reference output! No problem" - << " detected...\nbugpoint can't help you with your problem!\n"; - return false; + if (!ReduceMiscompilingPasses(*this).reduceList(PassesToRun, *Error)) { + if (Error->empty()) + errs() << "*** Optimized program matches reference output! No problem" + << " detected...\nbugpoint can't help you with your problem!\n"; + return; } outs() << "\n*** Found miscompiling pass" @@ -603,8 +680,10 @@ bool BugDriver::debugMiscompilation() { << getPassesString(getPassesToRun()) << '\n'; EmitProgressBitcode("passinput"); - std::vector<Function*> MiscompiledFunctions = - DebugAMiscompilation(*this, TestOptimizer); + std::vector<Function *> MiscompiledFunctions = + DebugAMiscompilation(*this, TestOptimizer, *Error); + if (!Error->empty()) + return; // Output a bunch of bitcode files for the user... outs() << "Outputting reduced bitcode files which expose the problem:\n"; @@ -624,7 +703,7 @@ bool BugDriver::debugMiscompilation() { EmitProgressBitcode("tooptimize"); setNewProgram(ToOptimize); // Delete hacked module. - return false; + return; } /// CleanupAndPrepareModules - Get the specified modules ready for code @@ -797,7 +876,8 @@ static void CleanupAndPrepareModules(BugDriver &BD, Module *&Test, /// the "Test" portion of the program is miscompiled by the code generator under /// test. If so, return true. In any case, both module arguments are deleted. /// -static bool TestCodeGenerator(BugDriver &BD, Module *Test, Module *Safe) { +static bool TestCodeGenerator(BugDriver &BD, Module *Test, Module *Safe, + std::string &Error) { CleanupAndPrepareModules(BD, Test, Safe); sys::Path TestModuleBC("bugpoint.test.bc"); @@ -827,12 +907,16 @@ static bool TestCodeGenerator(BugDriver &BD, Module *Test, Module *Safe) { << "'\nExiting."; exit(1); } - std::string SharedObject = BD.compileSharedObject(SafeModuleBC.str()); + std::string SharedObject = BD.compileSharedObject(SafeModuleBC.str(), Error); + if (!Error.empty()) + return -1; delete Safe; // Run the code generator on the `Test' code, loading the shared library. // The function returns whether or not the new output differs from reference. - int Result = BD.diffProgram(TestModuleBC.str(), SharedObject, false); + bool Result = BD.diffProgram(TestModuleBC.str(), SharedObject, false, &Error); + if (!Error.empty()) + return false; if (Result) errs() << ": still failing!\n"; @@ -848,23 +932,28 @@ static bool TestCodeGenerator(BugDriver &BD, Module *Test, Module *Safe) { /// debugCodeGenerator - debug errors in LLC, LLI, or CBE. /// -bool BugDriver::debugCodeGenerator() { +bool BugDriver::debugCodeGenerator(std::string *Error) { if ((void*)SafeInterpreter == (void*)Interpreter) { - std::string Result = executeProgramSafely("bugpoint.safe.out"); - outs() << "\n*** The \"safe\" i.e. 'known good' backend cannot match " - << "the reference diff. This may be due to a\n front-end " - << "bug or a bug in the original program, but this can also " - << "happen if bugpoint isn't running the program with the " - << "right flags or input.\n I left the result of executing " - << "the program with the \"safe\" backend in this file for " - << "you: '" - << Result << "'.\n"; + std::string Result = executeProgramSafely("bugpoint.safe.out", Error); + if (Error->empty()) { + outs() << "\n*** The \"safe\" i.e. 'known good' backend cannot match " + << "the reference diff. This may be due to a\n front-end " + << "bug or a bug in the original program, but this can also " + << "happen if bugpoint isn't running the program with the " + << "right flags or input.\n I left the result of executing " + << "the program with the \"safe\" backend in this file for " + << "you: '" + << Result << "'.\n"; + } return true; } DisambiguateGlobalSymbols(Program); - std::vector<Function*> Funcs = DebugAMiscompilation(*this, TestCodeGenerator); + std::vector<Function*> Funcs = DebugAMiscompilation(*this, TestCodeGenerator, + *Error); + if (!Error->empty()) + return true; // Split the module into the two halves of the program we want. DenseMap<const Value*, Value*> ValueMap; @@ -902,7 +991,9 @@ bool BugDriver::debugCodeGenerator() { << "'\nExiting."; exit(1); } - std::string SharedObject = compileSharedObject(SafeModuleBC.str()); + std::string SharedObject = compileSharedObject(SafeModuleBC.str(), *Error); + if (!Error->empty()) + return true; delete ToNotCodeGen; outs() << "You can reproduce the problem with the command line: \n"; @@ -919,7 +1010,7 @@ bool BugDriver::debugCodeGenerator() { outs() << "\n"; outs() << " " << TestModuleBC.str() << ".exe"; } - for (unsigned i=0, e = InputArgv.size(); i != e; ++i) + for (unsigned i = 0, e = InputArgv.size(); i != e; ++i) outs() << " " << InputArgv[i]; outs() << '\n'; outs() << "The shared object was created with:\n llc -march=c " diff --git a/tools/bugpoint/ToolRunner.cpp b/tools/bugpoint/ToolRunner.cpp index 62df0f1..9c8c2f9 100644 --- a/tools/bugpoint/ToolRunner.cpp +++ b/tools/bugpoint/ToolRunner.cpp @@ -50,11 +50,9 @@ namespace { cl::desc("Remote execution (rsh/ssh) extra options")); } -ToolExecutionError::~ToolExecutionError() throw() { } - /// RunProgramWithTimeout - This function provides an alternate interface /// to the sys::Program::ExecuteAndWait interface. -/// @see sys:Program::ExecuteAndWait +/// @see sys::Program::ExecuteAndWait static int RunProgramWithTimeout(const sys::Path &ProgramPath, const char **Args, const sys::Path &StdInFile, @@ -86,7 +84,7 @@ static int RunProgramWithTimeout(const sys::Path &ProgramPath, /// Returns the remote program exit code or reports a remote client error if it /// fails. Remote client is required to return 255 if it failed or program exit /// code otherwise. -/// @see sys:Program::ExecuteAndWait +/// @see sys::Program::ExecuteAndWait static int RunProgramRemotelyWithTimeout(const sys::Path &RemoteClientPath, const char **Args, const sys::Path &StdInFile, @@ -129,13 +127,13 @@ static int RunProgramRemotelyWithTimeout(const sys::Path &RemoteClientPath, ErrorFile.close(); } - throw ToolExecutionError(OS.str()); + errs() << OS; } return ReturnCode; } -static void ProcessFailure(sys::Path ProgPath, const char** Args) { +static std::string ProcessFailure(sys::Path ProgPath, const char** Args) { std::ostringstream OS; OS << "\nError running tool:\n "; for (const char **Arg = Args; *Arg; ++Arg) @@ -162,7 +160,7 @@ static void ProcessFailure(sys::Path ProgPath, const char** Args) { } ErrorFilename.eraseFromDisk(); - throw ToolExecutionError(OS.str()); + return OS.str(); } //===---------------------------------------------------------------------===// @@ -183,6 +181,7 @@ namespace { const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs, const std::vector<std::string> &SharedLibs = std::vector<std::string>(), @@ -195,6 +194,7 @@ int LLI::ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs, const std::vector<std::string> &SharedLibs, unsigned Timeout, @@ -263,9 +263,10 @@ namespace { const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs, const std::vector<std::string> &SharedLibs = - std::vector<std::string>(), + std::vector<std::string>(), unsigned Timeout = 0, unsigned MemoryLimit = 0); }; @@ -275,6 +276,7 @@ int CustomExecutor::ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs, const std::vector<std::string> &SharedLibs, unsigned Timeout, @@ -289,7 +291,7 @@ int CustomExecutor::ExecuteProgram(const std::string &Bitcode, ProgramArgs.push_back(0); // Add optional parameters to the running program from Argv - for (unsigned i=0, e = Args.size(); i != e; ++i) + for (unsigned i = 0, e = Args.size(); i != e; ++i) ProgramArgs.push_back(Args[i].c_str()); return RunProgramWithTimeout( @@ -351,7 +353,7 @@ AbstractInterpreter *AbstractInterpreter::createCustom( // LLC Implementation of AbstractIntepreter interface // GCC::FileType LLC::OutputCode(const std::string &Bitcode, - sys::Path &OutputAsmFile) { + sys::Path &OutputAsmFile, std::string &Error) { const char *Suffix = (UseIntegratedAssembler ? ".llc.o" : ".llc.s"); sys::Path uniqueFile(Bitcode + Suffix); std::string ErrMsg; @@ -379,20 +381,19 @@ GCC::FileType LLC::OutputCode(const std::string &Bitcode, outs() << (UseIntegratedAssembler ? "<llc-ia>" : "<llc>"); outs().flush(); DEBUG(errs() << "\nAbout to run:\t"; - for (unsigned i=0, e = LLCArgs.size()-1; i != e; ++i) + for (unsigned i = 0, e = LLCArgs.size()-1; i != e; ++i) errs() << " " << LLCArgs[i]; errs() << "\n"; ); if (RunProgramWithTimeout(sys::Path(LLCPath), &LLCArgs[0], sys::Path(), sys::Path(), sys::Path())) - ProcessFailure(sys::Path(LLCPath), &LLCArgs[0]); - - return UseIntegratedAssembler ? GCC::ObjectFile : GCC::AsmFile; + Error = ProcessFailure(sys::Path(LLCPath), &LLCArgs[0]); + return UseIntegratedAssembler ? GCC::ObjectFile : GCC::AsmFile; } -void LLC::compileProgram(const std::string &Bitcode) { +void LLC::compileProgram(const std::string &Bitcode, std::string *Error) { sys::Path OutputAsmFile; - OutputCode(Bitcode, OutputAsmFile); + OutputCode(Bitcode, OutputAsmFile, *Error); OutputAsmFile.eraseFromDisk(); } @@ -400,13 +401,14 @@ int LLC::ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &ArgsForGCC, const std::vector<std::string> &SharedLibs, unsigned Timeout, unsigned MemoryLimit) { sys::Path OutputAsmFile; - GCC::FileType FileKind = OutputCode(Bitcode, OutputAsmFile); + GCC::FileType FileKind = OutputCode(Bitcode, OutputAsmFile, *Error); FileRemover OutFileRemover(OutputAsmFile, !SaveTemps); std::vector<std::string> GCCArgs(ArgsForGCC); @@ -415,7 +417,7 @@ int LLC::ExecuteProgram(const std::string &Bitcode, // Assuming LLC worked, compile the result with GCC and run it. return gcc->ExecuteProgram(OutputAsmFile.str(), Args, FileKind, - InputFile, OutputFile, GCCArgs, + InputFile, OutputFile, Error, GCCArgs, Timeout, MemoryLimit); } @@ -460,12 +462,13 @@ namespace { const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs = std::vector<std::string>(), const std::vector<std::string> &SharedLibs = std::vector<std::string>(), - unsigned Timeout =0, - unsigned MemoryLimit =0); + unsigned Timeout = 0, + unsigned MemoryLimit = 0); }; } @@ -473,6 +476,7 @@ int JIT::ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs, const std::vector<std::string> &SharedLibs, unsigned Timeout, @@ -524,7 +528,7 @@ AbstractInterpreter *AbstractInterpreter::createJIT(const char *Argv0, } GCC::FileType CBE::OutputCode(const std::string &Bitcode, - sys::Path &OutputCFile) { + sys::Path &OutputCFile, std::string &Error) { sys::Path uniqueFile(Bitcode+".cbe.c"); std::string ErrMsg; if (uniqueFile.makeUnique(true, &ErrMsg)) { @@ -533,34 +537,34 @@ GCC::FileType CBE::OutputCode(const std::string &Bitcode, } OutputCFile = uniqueFile; std::vector<const char *> LLCArgs; - LLCArgs.push_back (LLCPath.c_str()); + LLCArgs.push_back(LLCPath.c_str()); // Add any extra LLC args. for (unsigned i = 0, e = ToolArgs.size(); i != e; ++i) LLCArgs.push_back(ToolArgs[i].c_str()); - LLCArgs.push_back ("-o"); - LLCArgs.push_back (OutputCFile.c_str()); // Output to the C file - LLCArgs.push_back ("-march=c"); // Output C language - LLCArgs.push_back ("-f"); // Overwrite as necessary... - LLCArgs.push_back (Bitcode.c_str()); // This is the input bitcode - LLCArgs.push_back (0); + LLCArgs.push_back("-o"); + LLCArgs.push_back(OutputCFile.c_str()); // Output to the C file + LLCArgs.push_back("-march=c"); // Output C language + LLCArgs.push_back("-f"); // Overwrite as necessary... + LLCArgs.push_back(Bitcode.c_str()); // This is the input bitcode + LLCArgs.push_back(0); outs() << "<cbe>"; outs().flush(); DEBUG(errs() << "\nAbout to run:\t"; - for (unsigned i=0, e = LLCArgs.size()-1; i != e; ++i) + for (unsigned i = 0, e = LLCArgs.size()-1; i != e; ++i) errs() << " " << LLCArgs[i]; errs() << "\n"; ); if (RunProgramWithTimeout(LLCPath, &LLCArgs[0], sys::Path(), sys::Path(), sys::Path())) - ProcessFailure(LLCPath, &LLCArgs[0]); + Error = ProcessFailure(LLCPath, &LLCArgs[0]); return GCC::CFile; } -void CBE::compileProgram(const std::string &Bitcode) { +void CBE::compileProgram(const std::string &Bitcode, std::string *Error) { sys::Path OutputCFile; - OutputCode(Bitcode, OutputCFile); + OutputCode(Bitcode, OutputCFile, *Error); OutputCFile.eraseFromDisk(); } @@ -568,12 +572,13 @@ int CBE::ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &ArgsForGCC, const std::vector<std::string> &SharedLibs, unsigned Timeout, unsigned MemoryLimit) { sys::Path OutputCFile; - OutputCode(Bitcode, OutputCFile); + OutputCode(Bitcode, OutputCFile, *Error); FileRemover CFileRemove(OutputCFile, !SaveTemps); @@ -581,7 +586,7 @@ int CBE::ExecuteProgram(const std::string &Bitcode, GCCArgs.insert(GCCArgs.end(), SharedLibs.begin(), SharedLibs.end()); return gcc->ExecuteProgram(OutputCFile.str(), Args, GCC::CFile, - InputFile, OutputFile, GCCArgs, + InputFile, OutputFile, Error, GCCArgs, Timeout, MemoryLimit); } @@ -631,6 +636,7 @@ int GCC::ExecuteProgram(const std::string &ProgramFile, FileType fileType, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &ArgsForGCC, unsigned Timeout, unsigned MemoryLimit) { @@ -694,14 +700,14 @@ int GCC::ExecuteProgram(const std::string &ProgramFile, outs() << "<gcc>"; outs().flush(); DEBUG(errs() << "\nAbout to run:\t"; - for (unsigned i=0, e = GCCArgs.size()-1; i != e; ++i) + for (unsigned i = 0, e = GCCArgs.size()-1; i != e; ++i) errs() << " " << GCCArgs[i]; errs() << "\n"; ); if (RunProgramWithTimeout(GCCPath, &GCCArgs[0], sys::Path(), sys::Path(), sys::Path())) { - ProcessFailure(GCCPath, &GCCArgs[0]); - exit(1); + *Error = ProcessFailure(GCCPath, &GCCArgs[0]); + return -1; } std::vector<const char*> ProgramArgs; @@ -734,14 +740,14 @@ int GCC::ExecuteProgram(const std::string &ProgramFile, } // Add optional parameters to the running program from Argv - for (unsigned i=0, e = Args.size(); i != e; ++i) + for (unsigned i = 0, e = Args.size(); i != e; ++i) ProgramArgs.push_back(Args[i].c_str()); ProgramArgs.push_back(0); // NULL terminator // Now that we have a binary, run it! outs() << "<program>"; outs().flush(); DEBUG(errs() << "\nAbout to run:\t"; - for (unsigned i=0, e = ProgramArgs.size()-1; i != e; ++i) + for (unsigned i = 0, e = ProgramArgs.size()-1; i != e; ++i) errs() << " " << ProgramArgs[i]; errs() << "\n"; ); @@ -749,7 +755,7 @@ int GCC::ExecuteProgram(const std::string &ProgramFile, FileRemover OutputBinaryRemover(OutputBinary, !SaveTemps); if (RemoteClientPath.isEmpty()) { - DEBUG(errs() << "<run locally>";); + DEBUG(errs() << "<run locally>"); return RunProgramWithTimeout(OutputBinary, &ProgramArgs[0], sys::Path(InputFile), sys::Path(OutputFile), sys::Path(OutputFile), Timeout, MemoryLimit); @@ -763,7 +769,8 @@ int GCC::ExecuteProgram(const std::string &ProgramFile, int GCC::MakeSharedObject(const std::string &InputFile, FileType fileType, std::string &OutputFile, - const std::vector<std::string> &ArgsForGCC) { + const std::vector<std::string> &ArgsForGCC, + std::string &Error) { sys::Path uniqueFilename(InputFile+LTDL_SHLIB_EXT); std::string ErrMsg; if (uniqueFilename.makeUnique(true, &ErrMsg)) { @@ -831,13 +838,13 @@ int GCC::MakeSharedObject(const std::string &InputFile, FileType fileType, outs() << "<gcc>"; outs().flush(); DEBUG(errs() << "\nAbout to run:\t"; - for (unsigned i=0, e = GCCArgs.size()-1; i != e; ++i) + for (unsigned i = 0, e = GCCArgs.size()-1; i != e; ++i) errs() << " " << GCCArgs[i]; errs() << "\n"; ); if (RunProgramWithTimeout(GCCPath, &GCCArgs[0], sys::Path(), sys::Path(), sys::Path())) { - ProcessFailure(GCCPath, &GCCArgs[0]); + Error = ProcessFailure(GCCPath, &GCCArgs[0]); return 1; } return 0; diff --git a/tools/bugpoint/ToolRunner.h b/tools/bugpoint/ToolRunner.h index 0f75e99..cba10f2 100644 --- a/tools/bugpoint/ToolRunner.h +++ b/tools/bugpoint/ToolRunner.h @@ -19,6 +19,7 @@ #include "llvm/ADT/Triple.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/SystemUtils.h" #include "llvm/System/Path.h" #include <exception> @@ -32,19 +33,6 @@ extern Triple TargetTriple; class CBE; class LLC; -/// ToolExecutionError - An instance of this class is thrown by the -/// AbstractInterpreter instances if there is an error running a tool (e.g., LLC -/// crashes) which prevents execution of the program. -/// -class ToolExecutionError : std::exception { - std::string Message; -public: - explicit ToolExecutionError(const std::string &M) : Message(M) {} - virtual ~ToolExecutionError() throw(); - virtual const char* what() const throw() { return Message.c_str(); } -}; - - //===---------------------------------------------------------------------===// // GCC abstraction // @@ -75,6 +63,7 @@ public: FileType fileType, const std::string &InputFile, const std::string &OutputFile, + std::string *Error = 0, const std::vector<std::string> &GCCArgs = std::vector<std::string>(), unsigned Timeout = 0, @@ -85,7 +74,8 @@ public: /// int MakeSharedObject(const std::string &InputFile, FileType fileType, std::string &OutputFile, - const std::vector<std::string> &ArgsForGCC); + const std::vector<std::string> &ArgsForGCC, + std::string &Error); }; @@ -118,26 +108,29 @@ public: /// compileProgram - Compile the specified program from bitcode to executable /// code. This does not produce any output, it is only used when debugging - /// the code generator. If the code generator fails, an exception should be - /// thrown, otherwise, this function will just return. - virtual void compileProgram(const std::string &Bitcode) {} + /// the code generator. It returns false if the code generator fails. + virtual void compileProgram(const std::string &Bitcode, std::string *Error) {} /// OutputCode - Compile the specified program from bitcode to code /// understood by the GCC driver (either C or asm). If the code generator - /// fails, an exception should be thrown, otherwise, this function returns the - /// type of code emitted. + /// fails, it sets Error, otherwise, this function returns the type of code + /// emitted. virtual GCC::FileType OutputCode(const std::string &Bitcode, - sys::Path &OutFile) { - throw std::string("OutputCode not supported by this AbstractInterpreter!"); + sys::Path &OutFile, std::string &Error) { + Error = "OutputCode not supported by this AbstractInterpreter!"; + return GCC::AsmFile; } - + /// ExecuteProgram - Run the specified bitcode file, emitting output to the - /// specified filename. This returns the exit code of the program. + /// specified filename. This sets RetVal to the exit code of the program or + /// returns false if a problem was encountered that prevented execution of + /// the program. /// virtual int ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs = std::vector<std::string>(), const std::vector<std::string> &SharedLibs = @@ -164,14 +157,14 @@ public: /// compileProgram - Compile the specified program from bitcode to executable /// code. This does not produce any output, it is only used when debugging - /// the code generator. If the code generator fails, an exception should be - /// thrown, otherwise, this function will just return. - virtual void compileProgram(const std::string &Bitcode); + /// the code generator. Returns false if the code generator fails. + virtual void compileProgram(const std::string &Bitcode, std::string *Error); virtual int ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs = std::vector<std::string>(), const std::vector<std::string> &SharedLibs = @@ -181,10 +174,10 @@ public: /// OutputCode - Compile the specified program from bitcode to code /// understood by the GCC driver (either C or asm). If the code generator - /// fails, an exception should be thrown, otherwise, this function returns the - /// type of code emitted. + /// fails, it sets Error, otherwise, this function returns the type of code + /// emitted. virtual GCC::FileType OutputCode(const std::string &Bitcode, - sys::Path &OutFile); + sys::Path &OutFile, std::string &Error); }; @@ -212,14 +205,14 @@ public: /// compileProgram - Compile the specified program from bitcode to executable /// code. This does not produce any output, it is only used when debugging - /// the code generator. If the code generator fails, an exception should be - /// thrown, otherwise, this function will just return. - virtual void compileProgram(const std::string &Bitcode); + /// the code generator. Returns false if the code generator fails. + virtual void compileProgram(const std::string &Bitcode, std::string *Error); virtual int ExecuteProgram(const std::string &Bitcode, const std::vector<std::string> &Args, const std::string &InputFile, const std::string &OutputFile, + std::string *Error, const std::vector<std::string> &GCCArgs = std::vector<std::string>(), const std::vector<std::string> &SharedLibs = @@ -227,9 +220,12 @@ public: unsigned Timeout = 0, unsigned MemoryLimit = 0); + /// OutputCode - Compile the specified program from bitcode to code + /// understood by the GCC driver (either C or asm). If the code generator + /// fails, it sets Error, otherwise, this function returns the type of code + /// emitted. virtual GCC::FileType OutputCode(const std::string &Bitcode, - sys::Path &OutFile); - + sys::Path &OutFile, std::string &Error); }; } // End llvm namespace diff --git a/tools/bugpoint/bugpoint.cpp b/tools/bugpoint/bugpoint.cpp index e14f31e..ba5234b 100644 --- a/tools/bugpoint/bugpoint.cpp +++ b/tools/bugpoint/bugpoint.cpp @@ -149,23 +149,11 @@ int main(int argc, char **argv) { // avoid filling up the disk, we prevent it sys::Process::PreventCoreFiles(); - try { - return D.run(); - } catch (ToolExecutionError &TEE) { - errs() << "Tool execution error: " << TEE.what() << '\n'; - } catch (const std::string& msg) { - errs() << argv[0] << ": " << msg << "\n"; - } catch (const std::bad_alloc&) { - errs() << "Oh no, a bugpoint process ran out of memory!\n" - "To increase the allocation limits for bugpoint child\n" - "processes, use the -mlimit option.\n"; - } catch (const std::exception &e) { - errs() << "Whoops, a std::exception leaked out of bugpoint: " - << e.what() << "\n" - << "This is a bug in bugpoint!\n"; - } catch (...) { - errs() << "Whoops, an exception leaked out of bugpoint. " - << "This is a bug in bugpoint!\n"; + std::string Error; + bool Failure = D.run(Error); + if (!Error.empty()) { + errs() << Error; + return 1; } - return 1; + return Failure; } |