Delta.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. //===- Delta.cpp - Delta Debugging Algorithm Implementation ---------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // This file contains the implementation for the Delta Debugging Algorithm:
  10. // it splits a given set of Targets (i.e. Functions, Instructions, BBs, etc.)
  11. // into chunks and tries to reduce the number chunks that are interesting.
  12. //
  13. //===----------------------------------------------------------------------===//
  14. #include "Delta.h"
  15. #include "ReducerWorkItem.h"
  16. #include "TestRunner.h"
  17. #include "Utils.h"
  18. #include "llvm/ADT/STLExtras.h"
  19. #include "llvm/Analysis/ModuleSummaryAnalysis.h"
  20. #include "llvm/Analysis/ProfileSummaryInfo.h"
  21. #include "llvm/Bitcode/BitcodeReader.h"
  22. #include "llvm/Bitcode/BitcodeWriter.h"
  23. #include "llvm/CodeGen/MachineFunction.h"
  24. #include "llvm/IR/Module.h"
  25. #include "llvm/IR/Verifier.h"
  26. #include "llvm/MC/TargetRegistry.h"
  27. #include "llvm/Support/CommandLine.h"
  28. #include "llvm/Support/MemoryBufferRef.h"
  29. #include "llvm/Support/ThreadPool.h"
  30. #include <fstream>
  31. #include <set>
  32. using namespace llvm;
  33. extern cl::OptionCategory LLVMReduceOptions;
  34. static cl::opt<bool> AbortOnInvalidReduction(
  35. "abort-on-invalid-reduction",
  36. cl::desc("Abort if any reduction results in invalid IR"),
  37. cl::cat(LLVMReduceOptions));
  38. static cl::opt<unsigned int> StartingGranularityLevel(
  39. "starting-granularity-level",
  40. cl::desc("Number of times to divide chunks prior to first test"),
  41. cl::cat(LLVMReduceOptions));
  42. #ifdef LLVM_ENABLE_THREADS
  43. static cl::opt<unsigned> NumJobs(
  44. "j",
  45. cl::desc("Maximum number of threads to use to process chunks. Set to 1 to "
  46. "disable parallelism."),
  47. cl::init(1), cl::cat(LLVMReduceOptions));
  48. #else
  49. unsigned NumJobs = 1;
  50. #endif
  51. /// Splits Chunks in half and prints them.
  52. /// If unable to split (when chunk size is 1) returns false.
  53. static bool increaseGranularity(std::vector<Chunk> &Chunks) {
  54. if (Verbose)
  55. errs() << "Increasing granularity...";
  56. std::vector<Chunk> NewChunks;
  57. bool SplitAny = false;
  58. for (Chunk C : Chunks) {
  59. if (C.End - C.Begin == 0)
  60. NewChunks.push_back(C);
  61. else {
  62. int Half = (C.Begin + C.End) / 2;
  63. NewChunks.push_back({C.Begin, Half});
  64. NewChunks.push_back({Half + 1, C.End});
  65. SplitAny = true;
  66. }
  67. }
  68. if (SplitAny) {
  69. Chunks = NewChunks;
  70. if (Verbose) {
  71. errs() << "Success! " << NewChunks.size() << " New Chunks:\n";
  72. for (auto C : Chunks) {
  73. errs() << '\t';
  74. C.print();
  75. errs() << '\n';
  76. }
  77. }
  78. }
  79. return SplitAny;
  80. }
  81. // Check if \p ChunkToCheckForUninterestingness is interesting. Returns the
  82. // modified module if the chunk resulted in a reduction.
  83. static std::unique_ptr<ReducerWorkItem>
  84. CheckChunk(const Chunk ChunkToCheckForUninterestingness,
  85. std::unique_ptr<ReducerWorkItem> Clone, const TestRunner &Test,
  86. ReductionFunc ExtractChunksFromModule,
  87. const DenseSet<Chunk> &UninterestingChunks,
  88. const std::vector<Chunk> &ChunksStillConsideredInteresting) {
  89. // Take all of ChunksStillConsideredInteresting chunks, except those we've
  90. // already deemed uninteresting (UninterestingChunks) but didn't remove
  91. // from ChunksStillConsideredInteresting yet, and additionally ignore
  92. // ChunkToCheckForUninterestingness chunk.
  93. std::vector<Chunk> CurrentChunks;
  94. CurrentChunks.reserve(ChunksStillConsideredInteresting.size() -
  95. UninterestingChunks.size() - 1);
  96. copy_if(ChunksStillConsideredInteresting, std::back_inserter(CurrentChunks),
  97. [&](const Chunk &C) {
  98. return C != ChunkToCheckForUninterestingness &&
  99. !UninterestingChunks.count(C);
  100. });
  101. // Generate Module with only Targets inside Current Chunks
  102. Oracle O(CurrentChunks);
  103. ExtractChunksFromModule(O, *Clone);
  104. // Some reductions may result in invalid IR. Skip such reductions.
  105. if (Clone->verify(&errs())) {
  106. if (AbortOnInvalidReduction) {
  107. errs() << "Invalid reduction, aborting.\n";
  108. Clone->print(errs());
  109. exit(1);
  110. }
  111. if (Verbose) {
  112. errs() << " **** WARNING | reduction resulted in invalid module, "
  113. "skipping\n";
  114. }
  115. return nullptr;
  116. }
  117. if (Verbose) {
  118. errs() << "Ignoring: ";
  119. ChunkToCheckForUninterestingness.print();
  120. for (const Chunk &C : UninterestingChunks)
  121. C.print();
  122. errs() << "\n";
  123. }
  124. if (!Clone->isReduced(Test)) {
  125. // Program became non-reduced, so this chunk appears to be interesting.
  126. if (Verbose)
  127. errs() << "\n";
  128. return nullptr;
  129. }
  130. return Clone;
  131. }
  132. static SmallString<0> ProcessChunkFromSerializedBitcode(
  133. const Chunk ChunkToCheckForUninterestingness, const TestRunner &Test,
  134. ReductionFunc ExtractChunksFromModule,
  135. const DenseSet<Chunk> &UninterestingChunks,
  136. ArrayRef<Chunk> ChunksStillConsideredInteresting, StringRef OriginalBC,
  137. std::atomic<bool> &AnyReduced) {
  138. LLVMContext Ctx;
  139. auto CloneMMM = std::make_unique<ReducerWorkItem>();
  140. MemoryBufferRef Data(OriginalBC, "<bc file>");
  141. CloneMMM->readBitcode(Data, Ctx, Test.getToolName());
  142. SmallString<0> Result;
  143. if (std::unique_ptr<ReducerWorkItem> ChunkResult =
  144. CheckChunk(ChunkToCheckForUninterestingness, std::move(CloneMMM),
  145. Test, ExtractChunksFromModule, UninterestingChunks,
  146. ChunksStillConsideredInteresting)) {
  147. raw_svector_ostream BCOS(Result);
  148. ChunkResult->writeBitcode(BCOS);
  149. // Communicate that the task reduced a chunk.
  150. AnyReduced = true;
  151. }
  152. return Result;
  153. }
  154. using SharedTaskQueue = std::deque<std::shared_future<SmallString<0>>>;
  155. static void waitAndDiscardResultsBarrier(SharedTaskQueue &TaskQueue) {
  156. while (!TaskQueue.empty()) {
  157. auto &Future = TaskQueue.front();
  158. Future.wait();
  159. TaskQueue.pop_front();
  160. }
  161. }
  162. /// Runs the Delta Debugging algorithm, splits the code into chunks and
  163. /// reduces the amount of chunks that are considered interesting by the
  164. /// given test. The number of chunks is determined by a preliminary run of the
  165. /// reduction pass where no change must be made to the module.
  166. void llvm::runDeltaPass(TestRunner &Test, ReductionFunc ExtractChunksFromModule,
  167. StringRef Message) {
  168. assert(!Test.getProgram().verify(&errs()) &&
  169. "input module is broken before making changes");
  170. errs() << "*** " << Message << "...\n";
  171. int Targets;
  172. {
  173. // Count the number of chunks by counting the number of calls to
  174. // Oracle::shouldKeep() but always returning true so no changes are
  175. // made.
  176. std::vector<Chunk> AllChunks = {{0, INT_MAX}};
  177. Oracle Counter(AllChunks);
  178. ExtractChunksFromModule(Counter, Test.getProgram());
  179. Targets = Counter.count();
  180. assert(!Test.getProgram().verify(&errs()) &&
  181. "input module is broken after counting chunks");
  182. assert(Test.getProgram().isReduced(Test) &&
  183. "input module no longer interesting after counting chunks");
  184. #ifndef NDEBUG
  185. // Make sure that the number of chunks does not change as we reduce.
  186. std::vector<Chunk> NoChunks = {{0, INT_MAX}};
  187. Oracle NoChunksCounter(NoChunks);
  188. std::unique_ptr<ReducerWorkItem> Clone =
  189. Test.getProgram().clone(Test.getTargetMachine());
  190. ExtractChunksFromModule(NoChunksCounter, *Clone);
  191. assert(Targets == NoChunksCounter.count() &&
  192. "number of chunks changes when reducing");
  193. #endif
  194. }
  195. if (!Targets) {
  196. if (Verbose)
  197. errs() << "\nNothing to reduce\n";
  198. errs() << "----------------------------\n";
  199. return;
  200. }
  201. std::vector<Chunk> ChunksStillConsideredInteresting = {{0, Targets - 1}};
  202. std::unique_ptr<ReducerWorkItem> ReducedProgram;
  203. for (unsigned int Level = 0; Level < StartingGranularityLevel; Level++) {
  204. increaseGranularity(ChunksStillConsideredInteresting);
  205. }
  206. std::atomic<bool> AnyReduced;
  207. std::unique_ptr<ThreadPool> ChunkThreadPoolPtr;
  208. if (NumJobs > 1)
  209. ChunkThreadPoolPtr =
  210. std::make_unique<ThreadPool>(hardware_concurrency(NumJobs));
  211. bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity;
  212. do {
  213. FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = false;
  214. DenseSet<Chunk> UninterestingChunks;
  215. // When running with more than one thread, serialize the original bitcode
  216. // to OriginalBC.
  217. SmallString<0> OriginalBC;
  218. if (NumJobs > 1) {
  219. raw_svector_ostream BCOS(OriginalBC);
  220. Test.getProgram().writeBitcode(BCOS);
  221. }
  222. SharedTaskQueue TaskQueue;
  223. for (auto I = ChunksStillConsideredInteresting.rbegin(),
  224. E = ChunksStillConsideredInteresting.rend();
  225. I != E; ++I) {
  226. std::unique_ptr<ReducerWorkItem> Result = nullptr;
  227. unsigned WorkLeft = std::distance(I, E);
  228. // Run in parallel mode, if the user requested more than one thread and
  229. // there are at least a few chunks to process.
  230. if (NumJobs > 1 && WorkLeft > 1) {
  231. unsigned NumInitialTasks = std::min(WorkLeft, unsigned(NumJobs));
  232. unsigned NumChunksProcessed = 0;
  233. ThreadPool &ChunkThreadPool = *ChunkThreadPoolPtr;
  234. TaskQueue.clear();
  235. AnyReduced = false;
  236. // Queue jobs to process NumInitialTasks chunks in parallel using
  237. // ChunkThreadPool. When the tasks are added to the pool, parse the
  238. // original module from OriginalBC with a fresh LLVMContext object. This
  239. // ensures that the cloned module of each task uses an independent
  240. // LLVMContext object. If a task reduces the input, serialize the result
  241. // back in the corresponding Result element.
  242. for (unsigned J = 0; J < NumInitialTasks; ++J) {
  243. Chunk ChunkToCheck = *(I + J);
  244. TaskQueue.emplace_back(ChunkThreadPool.async(
  245. ProcessChunkFromSerializedBitcode, ChunkToCheck, std::ref(Test),
  246. ExtractChunksFromModule, UninterestingChunks,
  247. ChunksStillConsideredInteresting, OriginalBC,
  248. std::ref(AnyReduced)));
  249. }
  250. // Start processing results of the queued tasks. We wait for the first
  251. // task in the queue to finish. If it reduced a chunk, we parse the
  252. // result and exit the loop.
  253. // Otherwise we will try to schedule a new task, if
  254. // * no other pending job reduced a chunk and
  255. // * we have not reached the end of the chunk.
  256. while (!TaskQueue.empty()) {
  257. auto &Future = TaskQueue.front();
  258. Future.wait();
  259. NumChunksProcessed++;
  260. SmallString<0> Res = Future.get();
  261. TaskQueue.pop_front();
  262. if (Res.empty()) {
  263. unsigned NumScheduledTasks = NumChunksProcessed + TaskQueue.size();
  264. if (!AnyReduced && I + NumScheduledTasks != E) {
  265. Chunk ChunkToCheck = *(I + NumScheduledTasks);
  266. TaskQueue.emplace_back(ChunkThreadPool.async(
  267. ProcessChunkFromSerializedBitcode, ChunkToCheck,
  268. std::ref(Test), ExtractChunksFromModule, UninterestingChunks,
  269. ChunksStillConsideredInteresting, OriginalBC,
  270. std::ref(AnyReduced)));
  271. }
  272. continue;
  273. }
  274. Result = std::make_unique<ReducerWorkItem>();
  275. MemoryBufferRef Data(StringRef(Res), "<bc file>");
  276. Result->readBitcode(Data, Test.getProgram().M->getContext(),
  277. Test.getToolName());
  278. break;
  279. }
  280. // If we broke out of the loop, we still need to wait for everything to
  281. // avoid race access to the chunk set.
  282. //
  283. // TODO: Create a way to kill remaining items we're ignoring; they could
  284. // take a long time.
  285. waitAndDiscardResultsBarrier(TaskQueue);
  286. // Forward I to the last chunk processed in parallel.
  287. I += NumChunksProcessed - 1;
  288. } else {
  289. Result =
  290. CheckChunk(*I, Test.getProgram().clone(Test.getTargetMachine()),
  291. Test, ExtractChunksFromModule, UninterestingChunks,
  292. ChunksStillConsideredInteresting);
  293. }
  294. if (!Result)
  295. continue;
  296. const Chunk ChunkToCheckForUninterestingness = *I;
  297. FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = true;
  298. UninterestingChunks.insert(ChunkToCheckForUninterestingness);
  299. ReducedProgram = std::move(Result);
  300. // FIXME: Report meaningful progress info
  301. Test.writeOutput(" **** SUCCESS | Saved new best reduction to ");
  302. }
  303. // Delete uninteresting chunks
  304. erase_if(ChunksStillConsideredInteresting,
  305. [&UninterestingChunks](const Chunk &C) {
  306. return UninterestingChunks.count(C);
  307. });
  308. } while (!ChunksStillConsideredInteresting.empty() &&
  309. (FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity ||
  310. increaseGranularity(ChunksStillConsideredInteresting)));
  311. // If we reduced the testcase replace it
  312. if (ReducedProgram)
  313. Test.setProgram(std::move(ReducedProgram));
  314. if (Verbose)
  315. errs() << "Couldn't increase anymore.\n";
  316. errs() << "----------------------------\n";
  317. }