xref: /llvm-project/llvm/tools/llvm-reduce/deltas/Delta.cpp (revision b80c4c82d6d4682d2cb177cdffd1aa951322d26b)
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 
15 #include "Delta.h"
16 #include "llvm/Transforms/Utils/Cloning.h"
17 
18 /// Writes IR code to the given Filepath
19 static bool writeProgramToFile(StringRef Filepath, int FD, const Module &M) {
20   ToolOutputFile Out(Filepath, FD);
21   M.print(Out.os(), /*AnnotationWriter=*/nullptr);
22   Out.os().close();
23 
24   if (!Out.os().has_error()) {
25     Out.keep();
26     return false;
27   }
28   return true;
29 }
30 
31 /// Creates a temporary (and unique) file inside the tmp folder and writes
32 /// the given module IR.
33 static SmallString<128> createTmpFile(Module *M, StringRef TmpDir) {
34   SmallString<128> UniqueFilepath;
35   int UniqueFD;
36 
37   std::error_code EC = sys::fs::createUniqueFile(TmpDir + "/tmp-%%%.ll",
38                                                  UniqueFD, UniqueFilepath);
39   if (EC) {
40     errs() << "Error making unique filename: " << EC.message() << "!\n";
41     exit(1);
42   }
43 
44   if (writeProgramToFile(UniqueFilepath, UniqueFD, *M)) {
45     errs() << "Error emitting bitcode to file '" << UniqueFilepath << "'!\n";
46     exit(1);
47   }
48   return UniqueFilepath;
49 }
50 
51 /// Prints the Chunk Indexes with the following format: [start, end], if
52 /// chunk is at minimum size (1), then it just displays [start].
53 static void printChunks(std::vector<Chunk> Chunks, bool Oneline = false) {
54   if (Chunks.empty()) {
55     outs() << "No Chunks";
56     return;
57   }
58 
59   for (auto C : Chunks) {
60     if (!Oneline)
61       outs() << '\t';
62     C.print();
63     if (!Oneline)
64       outs() << '\n';
65   }
66 }
67 
68 /// Counts the amount of lines for a given file
69 static unsigned getLines(StringRef Filepath) {
70   unsigned Lines = 0;
71   std::string CurrLine;
72   std::ifstream FileStream(Filepath);
73 
74   while (std::getline(FileStream, CurrLine))
75     ++Lines;
76 
77   return Lines;
78 }
79 
80 /// Splits Chunks in half and prints them.
81 /// If unable to split (when chunk size is 1) returns false.
82 static bool increaseGranularity(std::vector<Chunk> &Chunks) {
83   outs() << "Increasing granularity...";
84   std::vector<Chunk> NewChunks;
85   bool SplitOne = false;
86 
87   for (auto &C : Chunks) {
88     if (C.end - C.begin == 0)
89       NewChunks.push_back(C);
90     else {
91       unsigned Half = (C.begin + C.end) / 2;
92       NewChunks.push_back({C.begin, Half});
93       NewChunks.push_back({Half + 1, C.end});
94       SplitOne = true;
95     }
96   }
97   if (SplitOne) {
98     Chunks = NewChunks;
99     outs() << "Success! New Chunks:\n";
100     printChunks(Chunks);
101   }
102   return SplitOne;
103 }
104 
105 bool llvm::runTestWithoutChunks(
106     TestRunner &Test, std::function<void(const std::vector<Chunk> &, Module *)>
107                           ExtractChunksFromModule) {
108   std::unique_ptr<Module> Clone = CloneModule(*Test.getProgram());
109 
110   // Generate Module with only Targets inside Current Chunks
111   ExtractChunksFromModule({}, Clone.get());
112   // Write Module to tmp file
113   SmallString<128> CurrentFilepath =
114       createTmpFile(Clone.get(), Test.getTmpDir());
115   outs() << " | " << sys::path::filename(CurrentFilepath);
116 
117   // Completely reduced Program isn't interesting
118   if (!Test.run(CurrentFilepath))
119     return false;
120 
121   // Hooray! We reduced the testcase completely
122   Test.setReducedFilepath(CurrentFilepath);
123   Test.setProgram(std::move(Clone));
124   return true;
125 }
126 
127 /// Runs the Delta Debugging algorithm, splits the code into chunks and
128 /// reduces the amount of chunks that are considered interesting by the
129 /// given test.
130 void llvm::runDeltaPass(
131     TestRunner &Test, unsigned Targets,
132     std::function<void(const std::vector<Chunk> &, Module *)>
133         ExtractChunksFromModule) {
134   if (!Targets)
135     return;
136 
137   std::vector<Chunk> Chunks = {{1, Targets}};
138   std::set<Chunk> UninterestingChunks;
139   std::unique_ptr<Module> ReducedProgram;
140 
141   if (!Test.run(Test.getReducedFilepath())) {
142     outs() << "\nInput isn't interesting! Verify interesting-ness test\n";
143     outs() << "----------------------------\n";
144     return;
145   }
146 
147   if (!increaseGranularity(Chunks)) {
148     outs() << "\nCan't reduce anymore\n";
149     outs() << "----------------------------\n";
150     return;
151   }
152 
153   do {
154     UninterestingChunks = {};
155     for (int I = Chunks.size() - 1; I >= 0; --I) {
156       std::vector<Chunk> CurrentChunks;
157 
158       for (auto C : Chunks)
159         if (!UninterestingChunks.count(C) && C != Chunks[I])
160           CurrentChunks.push_back(C);
161 
162       if (CurrentChunks.empty())
163         continue;
164 
165       // Clone module before hacking it up..
166       std::unique_ptr<Module> CurrentProgram = CloneModule(*Test.getProgram());
167       // Generate Module with only Targets inside Current Chunks
168       ExtractChunksFromModule(CurrentChunks, CurrentProgram.get());
169       // Write Module to tmp file
170       SmallString<128> CurrentFilepath =
171           createTmpFile(CurrentProgram.get(), Test.getTmpDir());
172 
173       outs() << "Testing with: ";
174       printChunks(CurrentChunks, /*Oneline=*/true);
175       outs() << " | " << sys::path::filename(CurrentFilepath);
176 
177       // Current Chunks aren't interesting
178       if (!Test.run(CurrentFilepath)) {
179         outs() << "\n";
180         continue;
181       }
182 
183       UninterestingChunks.insert(Chunks[I]);
184       Test.setReducedFilepath(CurrentFilepath);
185       ReducedProgram = std::move(CurrentProgram);
186       outs() << " **** SUCCESS | lines: " << getLines(CurrentFilepath) << "\n";
187     }
188     // Delete uninteresting chunks
189     auto UnwantedChunks = Chunks.end();
190     UnwantedChunks = std::remove_if(Chunks.begin(), Chunks.end(),
191                                     [UninterestingChunks](const Chunk &C) {
192                                       return UninterestingChunks.count(C);
193                                     });
194     Chunks.erase(UnwantedChunks, Chunks.end());
195 
196   } while (!UninterestingChunks.empty() || increaseGranularity(Chunks));
197 
198   // If we reduced the testcase replace it
199   if (ReducedProgram)
200     Test.setProgram(std::move(ReducedProgram));
201   outs() << "Couldn't increase anymore.\n";
202   outs() << "----------------------------\n";
203 }