1 //===- Delta.cpp - Delta Debugging Algorithm Implementation ---------------===//
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
7 //===----------------------------------------------------------------------===//
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.
13 //===----------------------------------------------------------------------===//
16 #include "ReducerWorkItem.h"
17 #include "llvm/ADT/STLExtras.h"
18 #include "llvm/Bitcode/BitcodeReader.h"
19 #include "llvm/Bitcode/BitcodeWriter.h"
20 #include "llvm/IR/Verifier.h"
21 #include "llvm/Support/CommandLine.h"
22 #include "llvm/Support/ThreadPool.h"
23 #include "llvm/Support/ToolOutputFile.h"
29 static cl::opt
<bool> AbortOnInvalidReduction(
30 "abort-on-invalid-reduction",
31 cl::desc("Abort if any reduction results in invalid IR"));
33 static cl::opt
<unsigned int> StartingGranularityLevel(
34 "starting-granularity-level",
35 cl::desc("Number of times to divide chunks prior to first test"));
37 static cl::opt
<bool> TmpFilesAsBitcode(
38 "write-tmp-files-as-bitcode",
39 cl::desc("Write temporary files as bitcode, instead of textual IR"),
42 #ifdef LLVM_ENABLE_THREADS
43 static cl::opt
<unsigned> NumJobs(
45 cl::desc("Maximum number of threads to use to process chunks. Set to 1 to "
46 "disables parallelism."),
52 void writeOutput(ReducerWorkItem
&M
, llvm::StringRef Message
);
54 bool isReduced(ReducerWorkItem
&M
, TestRunner
&Test
,
55 SmallString
<128> &CurrentFilepath
) {
56 // Write ReducerWorkItem to tmp file
58 std::error_code EC
= sys::fs::createTemporaryFile(
59 "llvm-reduce", M
.isMIR() ? "mir" : (TmpFilesAsBitcode
? "bc" : "ll"), FD
,
62 errs() << "Error making unique filename: " << EC
.message() << "!\n";
66 if (TmpFilesAsBitcode
) {
67 llvm::raw_fd_ostream
OutStream(FD
, true);
68 WriteBitcodeToFile(M
, OutStream
);
70 if (OutStream
.has_error()) {
71 errs() << "Error emitting bitcode to file '" << CurrentFilepath
<< "'!\n";
72 sys::fs::remove(CurrentFilepath
);
75 bool Res
= Test
.run(CurrentFilepath
);
76 sys::fs::remove(CurrentFilepath
);
79 ToolOutputFile
Out(CurrentFilepath
, FD
);
80 M
.print(Out
.os(), /*AnnotationWriter=*/nullptr);
82 if (Out
.os().has_error()) {
83 errs() << "Error emitting bitcode to file '" << CurrentFilepath
<< "'!\n";
87 // Current Chunks aren't interesting
88 return Test
.run(CurrentFilepath
);
91 /// Counts the amount of lines for a given file
92 static int getLines(StringRef Filepath
) {
95 std::ifstream FileStream
{std::string(Filepath
)};
97 while (std::getline(FileStream
, CurrLine
))
103 /// Splits Chunks in half and prints them.
104 /// If unable to split (when chunk size is 1) returns false.
105 static bool increaseGranularity(std::vector
<Chunk
> &Chunks
) {
106 errs() << "Increasing granularity...";
107 std::vector
<Chunk
> NewChunks
;
108 bool SplitOne
= false;
110 for (auto &C
: Chunks
) {
111 if (C
.End
- C
.Begin
== 0)
112 NewChunks
.push_back(C
);
114 int Half
= (C
.Begin
+ C
.End
) / 2;
115 NewChunks
.push_back({C
.Begin
, Half
});
116 NewChunks
.push_back({Half
+ 1, C
.End
});
122 errs() << "Success! New Chunks:\n";
123 for (auto C
: Chunks
) {
131 // Check if \p ChunkToCheckForUninterestingness is interesting. Returns the
132 // modified module if the chunk resulted in a reduction.
133 template <typename T
>
134 static std::unique_ptr
<ReducerWorkItem
>
135 CheckChunk(Chunk
&ChunkToCheckForUninterestingness
,
136 std::unique_ptr
<ReducerWorkItem
> Clone
, TestRunner
&Test
,
137 function_ref
<void(Oracle
&, T
&)> ExtractChunksFromModule
,
138 std::set
<Chunk
> &UninterestingChunks
,
139 std::vector
<Chunk
> &ChunksStillConsideredInteresting
) {
140 // Take all of ChunksStillConsideredInteresting chunks, except those we've
141 // already deemed uninteresting (UninterestingChunks) but didn't remove
142 // from ChunksStillConsideredInteresting yet, and additionally ignore
143 // ChunkToCheckForUninterestingness chunk.
144 std::vector
<Chunk
> CurrentChunks
;
145 CurrentChunks
.reserve(ChunksStillConsideredInteresting
.size() -
146 UninterestingChunks
.size() - 1);
147 copy_if(ChunksStillConsideredInteresting
, std::back_inserter(CurrentChunks
),
148 [&](const Chunk
&C
) {
149 return !UninterestingChunks
.count(C
) &&
150 C
!= ChunkToCheckForUninterestingness
;
153 // Generate Module with only Targets inside Current Chunks
154 Oracle
O(CurrentChunks
);
155 ExtractChunksFromModule(O
, *Clone
);
157 // Some reductions may result in invalid IR. Skip such reductions.
158 if (verifyReducerWorkItem(*Clone
, &errs())) {
159 if (AbortOnInvalidReduction
) {
160 errs() << "Invalid reduction\n";
163 errs() << " **** WARNING | reduction resulted in invalid module, "
168 errs() << "Ignoring: ";
169 ChunkToCheckForUninterestingness
.print();
170 for (const Chunk
&C
: UninterestingChunks
)
173 SmallString
<128> CurrentFilepath
;
174 if (!isReduced(*Clone
, Test
, CurrentFilepath
)) {
175 // Program became non-reduced, so this chunk appears to be interesting.
182 template <typename T
>
183 SmallString
<0> ProcessChunkFromSerializedBitcode(
184 Chunk
&ChunkToCheckForUninterestingness
, TestRunner
&Test
,
185 function_ref
<void(Oracle
&, T
&)> ExtractChunksFromModule
,
186 std::set
<Chunk
> &UninterestingChunks
,
187 std::vector
<Chunk
> &ChunksStillConsideredInteresting
,
188 SmallString
<0> &OriginalBC
, std::atomic
<bool> &AnyReduced
) {
190 Expected
<std::unique_ptr
<Module
>> MOrErr
= parseBitcodeFile(
191 MemoryBufferRef(StringRef(OriginalBC
.data(), OriginalBC
.size()),
192 "<llvm-reduce tmp module>"),
195 report_fatal_error("Failed to read bitcode");
196 auto CloneMMM
= std::make_unique
<ReducerWorkItem
>();
197 CloneMMM
->M
= std::move(MOrErr
.get());
199 SmallString
<0> Result
;
200 if (std::unique_ptr
<ReducerWorkItem
> ChunkResult
=
201 CheckChunk(ChunkToCheckForUninterestingness
, std::move(CloneMMM
),
202 Test
, ExtractChunksFromModule
, UninterestingChunks
,
203 ChunksStillConsideredInteresting
)) {
204 raw_svector_ostream
BCOS(Result
);
205 WriteBitcodeToFile(*ChunkResult
->M
, BCOS
);
206 // Communicate that the task reduced a chunk.
212 /// Runs the Delta Debugging algorithm, splits the code into chunks and
213 /// reduces the amount of chunks that are considered interesting by the
214 /// given test. The number of chunks is determined by a preliminary run of the
215 /// reduction pass where no change must be made to the module.
216 template <typename T
>
217 void runDeltaPassInt(
219 function_ref
<void(Oracle
&, T
&)> ExtractChunksFromModule
) {
220 assert(!verifyReducerWorkItem(Test
.getProgram(), &errs()) &&
221 "input module is broken before making changes");
223 SmallString
<128> CurrentFilepath
;
224 if (!isReduced(Test
.getProgram(), Test
, CurrentFilepath
)) {
225 errs() << "\nInput isn't interesting! Verify interesting-ness test\n";
231 // Count the number of chunks by counting the number of calls to
232 // Oracle::shouldKeep() but always returning true so no changes are
234 std::vector
<Chunk
> AllChunks
= {{0, INT_MAX
}};
235 Oracle
Counter(AllChunks
);
236 ExtractChunksFromModule(Counter
, Test
.getProgram());
237 Targets
= Counter
.count();
239 assert(!verifyReducerWorkItem(Test
.getProgram(), &errs()) &&
240 "input module is broken after counting chunks");
241 assert(isReduced(Test
.getProgram(), Test
, CurrentFilepath
) &&
242 "input module no longer interesting after counting chunks");
245 // Make sure that the number of chunks does not change as we reduce.
246 std::vector
<Chunk
> NoChunks
;
247 Oracle
NoChunksCounter(NoChunks
);
248 std::unique_ptr
<ReducerWorkItem
> Clone
=
249 cloneReducerWorkItem(Test
.getProgram());
250 ExtractChunksFromModule(NoChunksCounter
, *Clone
);
251 assert(Targets
== NoChunksCounter
.count() &&
252 "number of chunks changes when reducing");
256 errs() << "\nNothing to reduce\n";
260 std::vector
<Chunk
> ChunksStillConsideredInteresting
= {{0, Targets
- 1}};
261 std::unique_ptr
<ReducerWorkItem
> ReducedProgram
;
263 for (unsigned int Level
= 0; Level
< StartingGranularityLevel
; Level
++) {
264 increaseGranularity(ChunksStillConsideredInteresting
);
267 std::atomic
<bool> AnyReduced
;
268 std::unique_ptr
<ThreadPool
> ChunkThreadPoolPtr
;
271 std::make_unique
<ThreadPool
>(hardware_concurrency(NumJobs
));
273 bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity
;
275 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity
= false;
277 std::set
<Chunk
> UninterestingChunks
;
279 // When running with more than one thread, serialize the original bitcode
281 SmallString
<0> OriginalBC
;
283 raw_svector_ostream
BCOS(OriginalBC
);
284 WriteBitcodeToFile(*Test
.getProgram().M
, BCOS
);
287 std::deque
<std::shared_future
<SmallString
<0>>> TaskQueue
;
288 for (auto I
= ChunksStillConsideredInteresting
.rbegin(),
289 E
= ChunksStillConsideredInteresting
.rend();
291 std::unique_ptr
<ReducerWorkItem
> Result
= nullptr;
292 unsigned WorkLeft
= std::distance(I
, E
);
294 // Run in parallel mode, if the user requested more than one thread and
295 // there are at least a few chunks to process.
296 if (NumJobs
> 1 && WorkLeft
> 1) {
297 unsigned NumInitialTasks
= std::min(WorkLeft
, unsigned(NumJobs
));
298 unsigned NumChunksProcessed
= 0;
300 ThreadPool
&ChunkThreadPool
= *ChunkThreadPoolPtr
;
304 // Queue jobs to process NumInitialTasks chunks in parallel using
305 // ChunkThreadPool. When the tasks are added to the pool, parse the
306 // original module from OriginalBC with a fresh LLVMContext object. This
307 // ensures that the cloned module of each task uses an independent
308 // LLVMContext object. If a task reduces the input, serialize the result
309 // back in the corresponding Result element.
310 for (unsigned J
= 0; J
< NumInitialTasks
; ++J
) {
311 TaskQueue
.emplace_back(ChunkThreadPool
.async(
312 [J
, I
, &Test
, &ExtractChunksFromModule
, &UninterestingChunks
,
313 &ChunksStillConsideredInteresting
, &OriginalBC
, &AnyReduced
]() {
314 return ProcessChunkFromSerializedBitcode(
315 *(I
+ J
), Test
, ExtractChunksFromModule
,
316 UninterestingChunks
, ChunksStillConsideredInteresting
,
317 OriginalBC
, AnyReduced
);
321 // Start processing results of the queued tasks. We wait for the first
322 // task in the queue to finish. If it reduced a chunk, we parse the
323 // result and exit the loop.
324 // Otherwise we will try to schedule a new task, if
325 // * no other pending job reduced a chunk and
326 // * we have not reached the end of the chunk.
327 while (!TaskQueue
.empty()) {
328 auto &Future
= TaskQueue
.front();
331 NumChunksProcessed
++;
332 SmallString
<0> Res
= Future
.get();
333 TaskQueue
.pop_front();
335 unsigned NumScheduledTasks
= NumChunksProcessed
+ TaskQueue
.size();
336 if (!AnyReduced
&& I
+ NumScheduledTasks
!= E
) {
337 Chunk
&ChunkToCheck
= *(I
+ NumScheduledTasks
);
338 TaskQueue
.emplace_back(ChunkThreadPool
.async(
339 [&Test
, &ExtractChunksFromModule
, &UninterestingChunks
,
340 &ChunksStillConsideredInteresting
, &OriginalBC
,
341 &ChunkToCheck
, &AnyReduced
]() {
342 return ProcessChunkFromSerializedBitcode(
343 ChunkToCheck
, Test
, ExtractChunksFromModule
,
344 UninterestingChunks
, ChunksStillConsideredInteresting
,
345 OriginalBC
, AnyReduced
);
351 Expected
<std::unique_ptr
<Module
>> MOrErr
= parseBitcodeFile(
352 MemoryBufferRef(StringRef(Res
.data(), Res
.size()),
353 "<llvm-reduce tmp module>"),
354 Test
.getProgram().M
->getContext());
356 report_fatal_error("Failed to read bitcode");
357 Result
= std::make_unique
<ReducerWorkItem
>();
358 Result
->M
= std::move(MOrErr
.get());
361 // Forward I to the last chunk processed in parallel.
362 I
+= NumChunksProcessed
- 1;
364 Result
= CheckChunk(*I
, cloneReducerWorkItem(Test
.getProgram()), Test
,
365 ExtractChunksFromModule
, UninterestingChunks
,
366 ChunksStillConsideredInteresting
);
372 Chunk
&ChunkToCheckForUninterestingness
= *I
;
373 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity
= true;
374 UninterestingChunks
.insert(ChunkToCheckForUninterestingness
);
375 ReducedProgram
= std::move(Result
);
376 errs() << " **** SUCCESS | lines: " << getLines(CurrentFilepath
) << "\n";
377 writeOutput(*ReducedProgram
, "Saved new best reduction to ");
379 // Delete uninteresting chunks
380 erase_if(ChunksStillConsideredInteresting
,
381 [&UninterestingChunks
](const Chunk
&C
) {
382 return UninterestingChunks
.count(C
);
384 } while (!ChunksStillConsideredInteresting
.empty() &&
385 (FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity
||
386 increaseGranularity(ChunksStillConsideredInteresting
)));
388 // If we reduced the testcase replace it
390 Test
.setProgram(std::move(ReducedProgram
));
391 errs() << "Couldn't increase anymore.\n";
394 void llvm::runDeltaPass(
396 function_ref
<void(Oracle
&, Module
&)> ExtractChunksFromModule
) {
397 runDeltaPassInt
<Module
>(Test
, ExtractChunksFromModule
);
400 void llvm::runDeltaPass(
402 function_ref
<void(Oracle
&, MachineFunction
&)> ExtractChunksFromModule
) {
403 runDeltaPassInt
<MachineFunction
>(Test
, ExtractChunksFromModule
);