15
15
#include " Delta.h"
16
16
#include " ReducerWorkItem.h"
17
17
#include " llvm/ADT/STLExtras.h"
18
+ #include " llvm/Bitcode/BitcodeReader.h"
18
19
#include " llvm/Bitcode/BitcodeWriter.h"
19
20
#include " llvm/IR/Verifier.h"
20
21
#include " llvm/Support/CommandLine.h"
22
+ #include " llvm/Support/ThreadPool.h"
21
23
#include " llvm/Support/ToolOutputFile.h"
22
24
#include < fstream>
23
25
#include < set>
@@ -37,6 +39,16 @@ static cl::opt<bool> TmpFilesAsBitcode(
37
39
cl::desc (" Write temporary files as bitcode, instead of textual IR" ),
38
40
cl::init(false ));
39
41
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
+ " disables parallelism." ),
47
+ cl::init(1 ));
48
+ #else
49
+ unsigned NumJobs = 1 ;
50
+ #endif
51
+
40
52
void writeOutput (ReducerWorkItem &M, llvm::StringRef Message);
41
53
42
54
bool isReduced (ReducerWorkItem &M, TestRunner &Test,
@@ -120,7 +132,8 @@ static bool increaseGranularity(std::vector<Chunk> &Chunks) {
120
132
// modified module if the chunk resulted in a reduction.
121
133
template <typename T>
122
134
static std::unique_ptr<ReducerWorkItem>
123
- CheckChunk (Chunk &ChunkToCheckForUninterestingness, TestRunner &Test,
135
+ CheckChunk (Chunk &ChunkToCheckForUninterestingness,
136
+ std::unique_ptr<ReducerWorkItem> Clone, TestRunner &Test,
124
137
function_ref<void (Oracle &, T &)> ExtractChunksFromModule,
125
138
std::set<Chunk> &UninterestingChunks,
126
139
std::vector<Chunk> &ChunksStillConsideredInteresting) {
@@ -137,9 +150,6 @@ CheckChunk(Chunk &ChunkToCheckForUninterestingness, TestRunner &Test,
137
150
C != ChunkToCheckForUninterestingness;
138
151
});
139
152
140
- // Clone module before hacking it up..
141
- std::unique_ptr<ReducerWorkItem> Clone =
142
- cloneReducerWorkItem (Test.getProgram ());
143
153
// Generate Module with only Targets inside Current Chunks
144
154
Oracle O (CurrentChunks);
145
155
ExtractChunksFromModule (O, *Clone);
@@ -169,6 +179,36 @@ CheckChunk(Chunk &ChunkToCheckForUninterestingness, TestRunner &Test,
169
179
return Clone;
170
180
}
171
181
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) {
189
+ LLVMContext Ctx;
190
+ Expected<std::unique_ptr<Module>> MOrErr = parseBitcodeFile (
191
+ MemoryBufferRef (StringRef (OriginalBC.data (), OriginalBC.size ()),
192
+ " <llvm-reduce tmp module>" ),
193
+ Ctx);
194
+ if (!MOrErr)
195
+ report_fatal_error (" Failed to read bitcode" );
196
+ auto CloneMMM = std::make_unique<ReducerWorkItem>();
197
+ CloneMMM->M = std::move (MOrErr.get ());
198
+
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.
207
+ AnyReduced = true ;
208
+ }
209
+ return Result;
210
+ }
211
+
172
212
// / Runs the Delta Debugging algorithm, splits the code into chunks and
173
213
// / reduces the amount of chunks that are considered interesting by the
174
214
// / given test.
@@ -207,19 +247,112 @@ void runDeltaPassInt(
207
247
increaseGranularity (ChunksStillConsideredInteresting);
208
248
}
209
249
250
+ std::atomic<bool > AnyReduced;
251
+ std::unique_ptr<ThreadPool> ChunkThreadPoolPtr;
252
+ if (NumJobs > 1 )
253
+ ChunkThreadPoolPtr =
254
+ std::make_unique<ThreadPool>(hardware_concurrency (NumJobs));
255
+
210
256
bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity;
211
257
do {
212
258
FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = false ;
213
259
214
260
std::set<Chunk> UninterestingChunks;
215
- for (Chunk &ChunkToCheckForUninterestingness :
216
- reverse (ChunksStillConsideredInteresting)) {
217
- std::unique_ptr<ReducerWorkItem> Result = CheckChunk (
218
- ChunkToCheckForUninterestingness, Test, ExtractChunksFromModule,
219
- UninterestingChunks, ChunksStillConsideredInteresting);
261
+
262
+ // When running with more than one thread, serialize the original bitcode
263
+ // to OriginalBC.
264
+ SmallString<0 > OriginalBC;
265
+ if (NumJobs > 1 ) {
266
+ raw_svector_ostream BCOS (OriginalBC);
267
+ WriteBitcodeToFile (*Test.getProgram ().M , BCOS);
268
+ }
269
+
270
+ std::deque<std::future<SmallString<0 >>> TaskQueue;
271
+ for (auto I = ChunksStillConsideredInteresting.rbegin (),
272
+ E = ChunksStillConsideredInteresting.rend ();
273
+ I != E; ++I) {
274
+ std::unique_ptr<ReducerWorkItem> Result = nullptr ;
275
+ unsigned WorkLeft = std::distance (I, E);
276
+
277
+ // Run in parallel mode, if the user requested more than one thread and
278
+ // there are at least a few chunks to process.
279
+ if (NumJobs > 1 && WorkLeft > 1 ) {
280
+ unsigned NumInitialTasks = std::min (WorkLeft, unsigned (NumJobs));
281
+ unsigned NumChunksProcessed = 0 ;
282
+
283
+ ThreadPool &ChunkThreadPool = *ChunkThreadPoolPtr;
284
+ TaskQueue.clear ();
285
+
286
+ AnyReduced = false ;
287
+ // Queue jobs to process NumInitialTasks chunks in parallel using
288
+ // ChunkThreadPool. When the tasks are added to the pool, parse the
289
+ // original module from OriginalBC with a fresh LLVMContext object. This
290
+ // ensures that the cloned module of each task uses an independent
291
+ // LLVMContext object. If a task reduces the input, serialize the result
292
+ // back in the corresponding Result element.
293
+ for (unsigned J = 0 ; J < NumInitialTasks; ++J) {
294
+ TaskQueue.emplace_back (ChunkThreadPool.async (
295
+ [J, I, &Test, &ExtractChunksFromModule, &UninterestingChunks,
296
+ &ChunksStillConsideredInteresting, &OriginalBC, &AnyReduced]() {
297
+ return ProcessChunkFromSerializedBitcode (
298
+ *(I + J), Test, ExtractChunksFromModule,
299
+ UninterestingChunks, ChunksStillConsideredInteresting,
300
+ OriginalBC, AnyReduced);
301
+ }));
302
+ }
303
+
304
+ // Start processing results of the queued tasks. We wait for the first
305
+ // task in the queue to finish. If it reduced a chunk, we parse the
306
+ // result and exit the loop.
307
+ // Otherwise we will try to schedule a new task, if
308
+ // * no other pending job reduced a chunk and
309
+ // * we have not reached the end of the chunk.
310
+ while (!TaskQueue.empty ()) {
311
+ auto &Future = TaskQueue.front ();
312
+ Future.wait ();
313
+
314
+ NumChunksProcessed++;
315
+ SmallString<0 > Res = Future.get ();
316
+ TaskQueue.pop_front ();
317
+ if (Res.empty ()) {
318
+ unsigned NumScheduledTasks = NumChunksProcessed + TaskQueue.size ();
319
+ if (!AnyReduced && I + NumScheduledTasks != E) {
320
+ Chunk &ChunkToCheck = *(I + NumScheduledTasks);
321
+ TaskQueue.emplace_back (ChunkThreadPool.async (
322
+ [&Test, &ExtractChunksFromModule, &UninterestingChunks,
323
+ &ChunksStillConsideredInteresting, &OriginalBC,
324
+ &ChunkToCheck, &AnyReduced]() {
325
+ return ProcessChunkFromSerializedBitcode (
326
+ ChunkToCheck, Test, ExtractChunksFromModule,
327
+ UninterestingChunks, ChunksStillConsideredInteresting,
328
+ OriginalBC, AnyReduced);
329
+ }));
330
+ }
331
+ continue ;
332
+ }
333
+
334
+ Expected<std::unique_ptr<Module>> MOrErr = parseBitcodeFile (
335
+ MemoryBufferRef (StringRef (Res.data (), Res.size ()),
336
+ " <llvm-reduce tmp module>" ),
337
+ Test.getProgram ().M ->getContext ());
338
+ if (!MOrErr)
339
+ report_fatal_error (" Failed to read bitcode" );
340
+ Result = std::make_unique<ReducerWorkItem>();
341
+ Result->M = std::move (MOrErr.get ());
342
+ break ;
343
+ }
344
+ // Forward I to the last chunk processed in parallel.
345
+ I += NumChunksProcessed - 1 ;
346
+ } else {
347
+ Result = CheckChunk (*I, cloneReducerWorkItem (Test.getProgram ()), Test,
348
+ ExtractChunksFromModule, UninterestingChunks,
349
+ ChunksStillConsideredInteresting);
350
+ }
351
+
220
352
if (!Result)
221
353
continue ;
222
354
355
+ Chunk &ChunkToCheckForUninterestingness = *I;
223
356
FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = true ;
224
357
UninterestingChunks.insert (ChunkToCheckForUninterestingness);
225
358
ReducedProgram = std::move (Result);
0 commit comments