xref: /freebsd/contrib/llvm-project/llvm/tools/bugpoint/ListReducer.h (revision ec0ea6efa1ad229d75c394c1a9b9cac33af2b1d3)
1 //===- ListReducer.h - Trim down list while retaining property --*- C++ -*-===//
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 class is to be used as a base class for operations that want to zero in
10 // on a subset of the input which still causes the bug we are tracking.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #ifndef LLVM_TOOLS_BUGPOINT_LISTREDUCER_H
15 #define LLVM_TOOLS_BUGPOINT_LISTREDUCER_H
16 
17 #include "llvm/Support/Error.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <algorithm>
20 #include <cstdlib>
21 #include <random>
22 #include <vector>
23 
24 namespace llvm {
25 
26 extern bool BugpointIsInterrupted;
27 
28 template <typename ElTy> struct ListReducer {
29   enum TestResult {
30     NoFailure,  // No failure of the predicate was detected
31     KeepSuffix, // The suffix alone satisfies the predicate
32     KeepPrefix  // The prefix alone satisfies the predicate
33   };
34 
35   virtual ~ListReducer() {}
36 
37   /// This virtual function should be overriden by subclasses to implement the
38   /// test desired.  The testcase is only required to test to see if the Kept
39   /// list still satisfies the property, but if it is going to check the prefix
40   /// anyway, it can.
41   virtual Expected<TestResult> doTest(std::vector<ElTy> &Prefix,
42                                       std::vector<ElTy> &Kept) = 0;
43 
44   /// This function attempts to reduce the length of the specified list while
45   /// still maintaining the "test" property.  This is the core of the "work"
46   /// that bugpoint does.
47   Expected<bool> reduceList(std::vector<ElTy> &TheList) {
48     std::vector<ElTy> empty;
49     std::mt19937 randomness(0x6e5ea738);  // Seed the random number generator
50     Expected<TestResult> Result = doTest(TheList, empty);
51     if (Error E = Result.takeError())
52       return std::move(E);
53     switch (*Result) {
54     case KeepPrefix:
55       if (TheList.size() == 1) // we are done, it's the base case and it fails
56         return true;
57       else
58         break; // there's definitely an error, but we need to narrow it down
59 
60     case KeepSuffix:
61       // cannot be reached!
62       llvm_unreachable("bugpoint ListReducer internal error: "
63                        "selected empty set.");
64 
65     case NoFailure:
66       return false; // there is no failure with the full set of passes/funcs!
67     }
68 
69     // Maximal number of allowed splitting iterations,
70     // before the elements are randomly shuffled.
71     const unsigned MaxIterationsWithoutProgress = 3;
72 
73     // Maximal number of allowed single-element trim iterations. We add a
74     // threshold here as single-element reductions may otherwise take a
75     // very long time to complete.
76     const unsigned MaxTrimIterationsWithoutBackJump = 3;
77     bool ShufflingEnabled = true;
78 
79   Backjump:
80     unsigned MidTop = TheList.size();
81     unsigned MaxIterations = MaxIterationsWithoutProgress;
82     unsigned NumOfIterationsWithoutProgress = 0;
83     while (MidTop > 1) { // Binary split reduction loop
84       // Halt if the user presses ctrl-c.
85       if (BugpointIsInterrupted) {
86         errs() << "\n\n*** Reduction Interrupted, cleaning up...\n\n";
87         return true;
88       }
89 
90       // If the loop doesn't make satisfying progress, try shuffling.
91       // The purpose of shuffling is to avoid the heavy tails of the
92       // distribution (improving the speed of convergence).
93       if (ShufflingEnabled && NumOfIterationsWithoutProgress > MaxIterations) {
94         std::vector<ElTy> ShuffledList(TheList);
95         llvm::shuffle(ShuffledList.begin(), ShuffledList.end(), randomness);
96         errs() << "\n\n*** Testing shuffled set...\n\n";
97         // Check that random shuffle doesn't lose the bug
98         Expected<TestResult> Result = doTest(ShuffledList, empty);
99         // TODO: Previously, this error was ignored and we treated it as if
100         // shuffling hid the bug. This should really either be consumeError if
101         // that behaviour was sensible, or we should propagate the error.
102         assert(!Result.takeError() && "Shuffling caused internal error?");
103 
104         if (*Result == KeepPrefix) {
105           // If the bug is still here, use the shuffled list.
106           TheList.swap(ShuffledList);
107           MidTop = TheList.size();
108           // Must increase the shuffling treshold to avoid the small
109           // probability of infinite looping without making progress.
110           MaxIterations += 2;
111           errs() << "\n\n*** Shuffling does not hide the bug...\n\n";
112         } else {
113           ShufflingEnabled = false; // Disable shuffling further on
114           errs() << "\n\n*** Shuffling hides the bug...\n\n";
115         }
116         NumOfIterationsWithoutProgress = 0;
117       }
118 
119       unsigned Mid = MidTop / 2;
120       std::vector<ElTy> Prefix(TheList.begin(), TheList.begin() + Mid);
121       std::vector<ElTy> Suffix(TheList.begin() + Mid, TheList.end());
122 
123       Expected<TestResult> Result = doTest(Prefix, Suffix);
124       if (Error E = Result.takeError())
125         return std::move(E);
126       switch (*Result) {
127       case KeepSuffix:
128         // The property still holds.  We can just drop the prefix elements, and
129         // shorten the list to the "kept" elements.
130         TheList.swap(Suffix);
131         MidTop = TheList.size();
132         // Reset progress treshold and progress counter
133         MaxIterations = MaxIterationsWithoutProgress;
134         NumOfIterationsWithoutProgress = 0;
135         break;
136       case KeepPrefix:
137         // The predicate still holds, shorten the list to the prefix elements.
138         TheList.swap(Prefix);
139         MidTop = TheList.size();
140         // Reset progress treshold and progress counter
141         MaxIterations = MaxIterationsWithoutProgress;
142         NumOfIterationsWithoutProgress = 0;
143         break;
144       case NoFailure:
145         // Otherwise the property doesn't hold.  Some of the elements we removed
146         // must be necessary to maintain the property.
147         MidTop = Mid;
148         NumOfIterationsWithoutProgress++;
149         break;
150       }
151     }
152 
153     // Probability of backjumping from the trimming loop back to the binary
154     // split reduction loop.
155     const int BackjumpProbability = 10;
156 
157     // Okay, we trimmed as much off the top and the bottom of the list as we
158     // could.  If there is more than two elements in the list, try deleting
159     // interior elements and testing that.
160     //
161     if (TheList.size() > 2) {
162       bool Changed = true;
163       std::vector<ElTy> EmptyList;
164       unsigned TrimIterations = 0;
165       while (Changed) { // Trimming loop.
166         Changed = false;
167 
168         // If the binary split reduction loop made an unfortunate sequence of
169         // splits, the trimming loop might be left off with a huge number of
170         // remaining elements (large search space). Backjumping out of that
171         // search space and attempting a different split can significantly
172         // improve the convergence speed.
173         if (std::rand() % 100 < BackjumpProbability)
174           goto Backjump;
175 
176         for (unsigned i = 1; i < TheList.size() - 1; ++i) {
177           // Check interior elts
178           if (BugpointIsInterrupted) {
179             errs() << "\n\n*** Reduction Interrupted, cleaning up...\n\n";
180             return true;
181           }
182 
183           std::vector<ElTy> TestList(TheList);
184           TestList.erase(TestList.begin() + i);
185 
186           Expected<TestResult> Result = doTest(EmptyList, TestList);
187           if (Error E = Result.takeError())
188             return std::move(E);
189           if (*Result == KeepSuffix) {
190             // We can trim down the list!
191             TheList.swap(TestList);
192             --i; // Don't skip an element of the list
193             Changed = true;
194           }
195         }
196         if (TrimIterations >= MaxTrimIterationsWithoutBackJump)
197           break;
198         TrimIterations++;
199       }
200     }
201 
202     return true; // there are some failure and we've narrowed them down
203   }
204 };
205 
206 } // End llvm namespace
207 
208 #endif
209