[sanitizer] Improve FreeBSD ASLR detection
[llvm-project.git] / llvm / tools / llvm-reduce / deltas / Delta.cpp
blob1bab82f823c02af7711914b56ee94271bec04cee
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.
13 //===----------------------------------------------------------------------===//
15 #include "Delta.h"
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"
24 #include <fstream>
25 #include <set>
27 using namespace llvm;
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"),
40 cl::init(false));
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
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
57 int FD;
58 std::error_code EC = sys::fs::createTemporaryFile(
59 "llvm-reduce", M.isMIR() ? "mir" : (TmpFilesAsBitcode ? "bc" : "ll"), FD,
60 CurrentFilepath);
61 if (EC) {
62 errs() << "Error making unique filename: " << EC.message() << "!\n";
63 exit(1);
66 if (TmpFilesAsBitcode) {
67 llvm::raw_fd_ostream OutStream(FD, true);
68 WriteBitcodeToFile(M, OutStream);
69 OutStream.close();
70 if (OutStream.has_error()) {
71 errs() << "Error emitting bitcode to file '" << CurrentFilepath << "'!\n";
72 sys::fs::remove(CurrentFilepath);
73 exit(1);
75 bool Res = Test.run(CurrentFilepath);
76 sys::fs::remove(CurrentFilepath);
77 return Res;
79 ToolOutputFile Out(CurrentFilepath, FD);
80 M.print(Out.os(), /*AnnotationWriter=*/nullptr);
81 Out.os().close();
82 if (Out.os().has_error()) {
83 errs() << "Error emitting bitcode to file '" << CurrentFilepath << "'!\n";
84 exit(1);
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) {
93 int Lines = 0;
94 std::string CurrLine;
95 std::ifstream FileStream{std::string(Filepath)};
97 while (std::getline(FileStream, CurrLine))
98 ++Lines;
100 return Lines;
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);
113 else {
114 int Half = (C.Begin + C.End) / 2;
115 NewChunks.push_back({C.Begin, Half});
116 NewChunks.push_back({Half + 1, C.End});
117 SplitOne = true;
120 if (SplitOne) {
121 Chunks = NewChunks;
122 errs() << "Success! New Chunks:\n";
123 for (auto C : Chunks) {
124 errs() << '\t';
125 C.print();
126 errs() << '\n';
129 return SplitOne;
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";
161 exit(1);
163 errs() << " **** WARNING | reduction resulted in invalid module, "
164 "skipping\n";
165 return nullptr;
168 errs() << "Ignoring: ";
169 ChunkToCheckForUninterestingness.print();
170 for (const Chunk &C : UninterestingChunks)
171 C.print();
173 SmallString<128> CurrentFilepath;
174 if (!isReduced(*Clone, Test, CurrentFilepath)) {
175 // Program became non-reduced, so this chunk appears to be interesting.
176 errs() << "\n";
177 return nullptr;
179 return Clone;
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());
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;
209 return Result;
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(
218 TestRunner &Test,
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";
226 exit(1);
229 int Targets;
231 // Count the number of chunks by counting the number of calls to
232 // Oracle::shouldKeep() but always returning true so no changes are
233 // made.
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");
244 #ifndef NDEBUG
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");
253 #endif
255 if (!Targets) {
256 errs() << "\nNothing to reduce\n";
257 return;
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;
269 if (NumJobs > 1)
270 ChunkThreadPoolPtr =
271 std::make_unique<ThreadPool>(hardware_concurrency(NumJobs));
273 bool FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity;
274 do {
275 FoundAtLeastOneNewUninterestingChunkWithCurrentGranularity = false;
277 std::set<Chunk> UninterestingChunks;
279 // When running with more than one thread, serialize the original bitcode
280 // to OriginalBC.
281 SmallString<0> OriginalBC;
282 if (NumJobs > 1) {
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();
290 I != E; ++I) {
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;
301 TaskQueue.clear();
303 AnyReduced = false;
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);
318 }));
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();
329 Future.wait();
331 NumChunksProcessed++;
332 SmallString<0> Res = Future.get();
333 TaskQueue.pop_front();
334 if (Res.empty()) {
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);
346 }));
348 continue;
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());
355 if (!MOrErr)
356 report_fatal_error("Failed to read bitcode");
357 Result = std::make_unique<ReducerWorkItem>();
358 Result->M = std::move(MOrErr.get());
359 break;
361 // Forward I to the last chunk processed in parallel.
362 I += NumChunksProcessed - 1;
363 } else {
364 Result = CheckChunk(*I, cloneReducerWorkItem(Test.getProgram()), Test,
365 ExtractChunksFromModule, UninterestingChunks,
366 ChunksStillConsideredInteresting);
369 if (!Result)
370 continue;
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
389 if (ReducedProgram)
390 Test.setProgram(std::move(ReducedProgram));
391 errs() << "Couldn't increase anymore.\n";
394 void llvm::runDeltaPass(
395 TestRunner &Test,
396 function_ref<void(Oracle &, Module &)> ExtractChunksFromModule) {
397 runDeltaPassInt<Module>(Test, ExtractChunksFromModule);
400 void llvm::runDeltaPass(
401 TestRunner &Test,
402 function_ref<void(Oracle &, MachineFunction &)> ExtractChunksFromModule) {
403 runDeltaPassInt<MachineFunction>(Test, ExtractChunksFromModule);