Index: llvm/test/tools/llvm-reduce/Inputs/sleep-and-check-stores.py =================================================================== --- llvm/test/tools/llvm-reduce/Inputs/sleep-and-check-stores.py +++ llvm/test/tools/llvm-reduce/Inputs/sleep-and-check-stores.py @@ -18,11 +18,10 @@ if "store" in line: InterestingStores += 1 -print("Interesting stores ", InterestingStores, " sleeping ", sleep_seconds) time.sleep(sleep_seconds) -if InterestingStores > num_stores: +if InterestingStores >= num_stores: sys.exit(0) # interesting! sys.exit(1) # IR isn't interesting Index: llvm/test/tools/llvm-reduce/parallel-workitem-kill.ll =================================================================== --- llvm/test/tools/llvm-reduce/parallel-workitem-kill.ll +++ llvm/test/tools/llvm-reduce/parallel-workitem-kill.ll @@ -1,5 +1,5 @@ ; REQUIRES: thread_support -; RUN: llvm-reduce -j 4 %s -o %t --delta-passes=instructions --test %python --test-arg %S/Inputs/sleep-and-check-stores.py --test-arg 1 --test-arg 5 +; RUN: llvm-reduce --process-poll-interval=1 -j 4 %s -o %t --delta-passes=instructions --test %python --test-arg %S/Inputs/sleep-and-check-stores.py --test-arg 2 --test-arg 6 ; RUN: FileCheck %s < %t ; CHECK: define void @foo Index: llvm/tools/llvm-reduce/TestRunner.h =================================================================== --- llvm/tools/llvm-reduce/TestRunner.h +++ llvm/tools/llvm-reduce/TestRunner.h @@ -33,7 +33,7 @@ /// Runs the interesting-ness test for the specified file /// @returns 0 if test was successful, 1 if otherwise - int run(StringRef Filename) const; + int run(StringRef Filename, const std::atomic &Killed) const; /// Returns the most reduced version of the original testcase ReducerWorkItem &getProgram() const { return *Program; } Index: llvm/tools/llvm-reduce/TestRunner.cpp =================================================================== --- llvm/tools/llvm-reduce/TestRunner.cpp +++ llvm/tools/llvm-reduce/TestRunner.cpp @@ -18,6 +18,12 @@ using namespace llvm; +extern cl::OptionCategory LLVMReduceOptions; +static cl::opt PollInterval("process-poll-interval", + cl::desc("child process wait polling"), + cl::init(5), cl::Hidden, + cl::cat(LLVMReduceOptions)); + TestRunner::TestRunner(StringRef TestName, const std::vector &TestArgs, std::unique_ptr Program, @@ -37,7 +43,7 @@ /// Runs the interestingness test, passes file to be tested as first argument /// and other specified test arguments after that. -int TestRunner::run(StringRef Filename) const { +int TestRunner::run(StringRef Filename, const std::atomic &Killed) const { std::vector ProgramArgs; ProgramArgs.push_back(TestName); @@ -47,13 +53,13 @@ ProgramArgs.push_back(Filename); std::string ErrMsg; + bool ExecutionFailed; + sys::ProcessInfo PI = + sys::ExecuteNoWait(TestName, ProgramArgs, /*Env=*/std::nullopt, + Verbose ? DefaultRedirects : NullRedirects, + /*MemoryLimit=*/0, &ErrMsg, &ExecutionFailed); - int Result = - sys::ExecuteAndWait(TestName, ProgramArgs, /*Env=*/std::nullopt, - Verbose ? DefaultRedirects : NullRedirects, - /*SecondsToWait=*/0, /*MemoryLimit=*/0, &ErrMsg); - - if (Result < 0) { + if (ExecutionFailed) { Error E = make_error("Error running interesting-ness test: " + ErrMsg, inconvertibleErrorCode()); @@ -61,7 +67,30 @@ exit(1); } - return !Result; + // Poll every few seconds, taking a break to check if we should try to kill + // the process. We're trying to early exit on long running parallel reductions + // once we know they don't matter. + std::optional SecondsToWait(PollInterval); + sys::ProcessInfo WaitPI; + bool Polling = true; + + while (true) { + WaitPI = sys::Wait(PI, SecondsToWait, &ErrMsg, nullptr, /*Polling=*/Polling); + if (WaitPI.Pid == PI.Pid) + break; + + // TODO: This should probably be std::atomic_flag + if (Killed && WaitPI.Pid != -1) { + // The current Program API does not have a way to directly kill, so we're + // stuck using a 1 second timeout. + //sys::Wait(PI, 1, &ErrMsg, nullptr, false); + //return false; + Polling = false; + SecondsToWait = 1; + } + } + + return !WaitPI.ReturnCode; } void TestRunner::setProgram(std::unique_ptr P) { Index: llvm/tools/llvm-reduce/deltas/Delta.cpp =================================================================== --- llvm/tools/llvm-reduce/deltas/Delta.cpp +++ llvm/tools/llvm-reduce/deltas/Delta.cpp @@ -65,7 +65,8 @@ void readBitcode(ReducerWorkItem &M, MemoryBufferRef Data, LLVMContext &Ctx, StringRef ToolName); -bool isReduced(ReducerWorkItem &M, const TestRunner &Test) { +bool isReduced(ReducerWorkItem &M, const TestRunner &Test, + const std::atomic &Killed) { const bool UseBitcode = Test.inputIsBitcode() || TmpFilesAsBitcode; SmallString<128> CurrentFilepath; @@ -96,7 +97,7 @@ } // Current Chunks aren't interesting - return Test.run(CurrentFilepath); + return Test.run(CurrentFilepath, Killed); } /// Splits Chunks in half and prints them. @@ -138,7 +139,8 @@ std::unique_ptr Clone, const TestRunner &Test, ReductionFunc ExtractChunksFromModule, const DenseSet &UninterestingChunks, - const std::vector &ChunksStillConsideredInteresting) { + const std::vector &ChunksStillConsideredInteresting, + const std::atomic &Killed) { // Take all of ChunksStillConsideredInteresting chunks, except those we've // already deemed uninteresting (UninterestingChunks) but didn't remove // from ChunksStillConsideredInteresting yet, and additionally ignore @@ -178,7 +180,7 @@ errs() << "\n"; } - if (!isReduced(*Clone, Test)) { + if (!isReduced(*Clone, Test, Killed)) { // Program became non-reduced, so this chunk appears to be interesting. if (Verbose) errs() << "\n"; @@ -191,7 +193,8 @@ Chunk &ChunkToCheckForUninterestingness, TestRunner &Test, ReductionFunc ExtractChunksFromModule, DenseSet &UninterestingChunks, std::vector &ChunksStillConsideredInteresting, - SmallString<0> &OriginalBC, std::atomic &AnyReduced) { + SmallString<0> &OriginalBC, std::atomic &AnyReduced, + const std::atomic &Killed) { LLVMContext Ctx; auto CloneMMM = std::make_unique(); MemoryBufferRef Data(StringRef(OriginalBC), ""); @@ -201,7 +204,7 @@ if (std::unique_ptr ChunkResult = CheckChunk(ChunkToCheckForUninterestingness, std::move(CloneMMM), Test, ExtractChunksFromModule, UninterestingChunks, - ChunksStillConsideredInteresting)) { + ChunksStillConsideredInteresting, Killed)) { raw_svector_ostream BCOS(Result); writeBitcode(*ChunkResult, BCOS); // Communicate that the task reduced a chunk. @@ -242,7 +245,7 @@ assert(!verifyReducerWorkItem(Test.getProgram(), &errs()) && "input module is broken after counting chunks"); - assert(isReduced(Test.getProgram(), Test) && + assert(isReduced(Test.getProgram(), Test, std::atomic()) && "input module no longer interesting after counting chunks"); #ifndef NDEBUG @@ -290,6 +293,11 @@ writeBitcode(Test.getProgram(), BCOS); } + // If doing parallel reduction, signal to running workitem threads that we + // no longer care about their results. They should try to kill the reducer + // workitem process and exit. + std::atomic Killed = false; + SharedTaskQueue TaskQueue; for (auto I = ChunksStillConsideredInteresting.rbegin(), E = ChunksStillConsideredInteresting.rend(); @@ -316,11 +324,12 @@ for (unsigned J = 0; J < NumInitialTasks; ++J) { TaskQueue.emplace_back(ChunkThreadPool.async( [J, I, &Test, &ExtractChunksFromModule, &UninterestingChunks, - &ChunksStillConsideredInteresting, &OriginalBC, &AnyReduced]() { + &ChunksStillConsideredInteresting, &OriginalBC, &AnyReduced, + &Killed]() { return ProcessChunkFromSerializedBitcode( *(I + J), Test, ExtractChunksFromModule, UninterestingChunks, ChunksStillConsideredInteresting, - OriginalBC, AnyReduced); + OriginalBC, AnyReduced, Killed); })); } @@ -344,11 +353,11 @@ TaskQueue.emplace_back(ChunkThreadPool.async( [&Test, &ExtractChunksFromModule, &UninterestingChunks, &ChunksStillConsideredInteresting, &OriginalBC, - &ChunkToCheck, &AnyReduced]() { + &ChunkToCheck, &AnyReduced, &Killed]() { return ProcessChunkFromSerializedBitcode( ChunkToCheck, Test, ExtractChunksFromModule, UninterestingChunks, ChunksStillConsideredInteresting, - OriginalBC, AnyReduced); + OriginalBC, AnyReduced, Killed); })); } continue; @@ -361,6 +370,8 @@ break; } + Killed = true; + // If we broke out of the loop, we still need to wait for everything to // avoid race access to the chunk set. // @@ -375,7 +386,7 @@ *I, cloneReducerWorkItem(Test.getProgram(), Test.getTargetMachine()), Test, ExtractChunksFromModule, UninterestingChunks, - ChunksStillConsideredInteresting); + ChunksStillConsideredInteresting, Killed); } if (!Result) Index: llvm/tools/llvm-reduce/llvm-reduce.cpp =================================================================== --- llvm/tools/llvm-reduce/llvm-reduce.cpp +++ llvm/tools/llvm-reduce/llvm-reduce.cpp @@ -101,7 +101,8 @@ static codegen::RegisterCodeGenFlags CGF; -bool isReduced(ReducerWorkItem &M, const TestRunner &Test); +bool isReduced(ReducerWorkItem &M, const TestRunner &Test, + const std::atomic &Killed); /// Turn off crash debugging features /// @@ -217,7 +218,7 @@ // test, rather than evaluating the source IR directly. This is for the // convenience of lit tests; the stripped out comments may have broken the // interestingness checks. - if (!isReduced(Tester.getProgram(), Tester)) { + if (!isReduced(Tester.getProgram(), Tester, std::atomic())) { errs() << "\nInput isn't interesting! Verify interesting-ness test\n"; return 1; }