• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "source/fuzz/pass_management/repeated_pass_recommender_standard.h"
16 
17 #include <numeric>
18 
19 namespace spvtools {
20 namespace fuzz {
21 
RepeatedPassRecommenderStandard(RepeatedPassInstances * pass_instances,FuzzerContext * fuzzer_context)22 RepeatedPassRecommenderStandard::RepeatedPassRecommenderStandard(
23     RepeatedPassInstances* pass_instances, FuzzerContext* fuzzer_context)
24     : pass_instances_(pass_instances), fuzzer_context_(fuzzer_context) {}
25 
26 RepeatedPassRecommenderStandard::~RepeatedPassRecommenderStandard() = default;
27 
28 std::vector<FuzzerPass*>
GetFuturePassRecommendations(const FuzzerPass & pass)29 RepeatedPassRecommenderStandard::GetFuturePassRecommendations(
30     const FuzzerPass& pass) {
31   if (&pass == pass_instances_->GetAddAccessChains()) {
32     // - Adding access chains means there is more scope for loading and storing
33     // - It could be worth making more access chains from the recently-added
34     //   access chains
35     return RandomOrderAndNonNull({pass_instances_->GetAddLoads(),
36                                   pass_instances_->GetAddStores(),
37                                   pass_instances_->GetAddAccessChains()});
38   }
39   if (&pass == pass_instances_->GetAddBitInstructionSynonyms()) {
40     // - Adding bit instruction synonyms creates opportunities to apply synonyms
41     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
42   }
43   if (&pass == pass_instances_->GetAddCompositeExtract()) {
44     // - This transformation can introduce synonyms to the fact manager.
45     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
46   }
47   if (&pass == pass_instances_->GetAddCompositeInserts()) {
48     // - Having added inserts we will have more vectors, so there is scope for
49     //   vector shuffling
50     // - Adding inserts creates synonyms, which we should try to use
51     // - Vector inserts can be made dynamic
52     return RandomOrderAndNonNull(
53         {pass_instances_->GetAddVectorShuffleInstructions(),
54          pass_instances_->GetApplyIdSynonyms(),
55          pass_instances_->GetMakeVectorOperationsDynamic()});
56   }
57   if (&pass == pass_instances_->GetAddCompositeTypes()) {
58     // - More composite types gives more scope for constructing composites
59     return RandomOrderAndNonNull({pass_instances_->GetConstructComposites()});
60   }
61   if (&pass == pass_instances_->GetAddCopyMemory()) {
62     // - Recently-added copy memories could be replace with load-store pairs
63     return RandomOrderAndNonNull(
64         {pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()});
65   }
66   if (&pass == pass_instances_->GetAddDeadBlocks()) {
67     // - Dead blocks are great for adding function calls
68     // - Dead blocks are also great for adding loads and stores
69     // - The guard associated with a dead block can be obfuscated
70     // - Branches from dead blocks may be replaced with exits
71     return RandomOrderAndNonNull(
72         {pass_instances_->GetAddFunctionCalls(), pass_instances_->GetAddLoads(),
73          pass_instances_->GetAddStores(),
74          pass_instances_->GetObfuscateConstants(),
75          pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
76   }
77   if (&pass == pass_instances_->GetAddDeadBreaks()) {
78     // - The guard of the dead break is a good candidate for obfuscation
79     return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
80   }
81   if (&pass == pass_instances_->GetAddDeadContinues()) {
82     // - The guard of the dead continue is a good candidate for obfuscation
83     return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants()});
84   }
85   if (&pass == pass_instances_->GetAddEquationInstructions()) {
86     // - Equation instructions can create synonyms, which we can apply
87     // - Equation instructions collaborate with one another to make synonyms, so
88     //   having added some it is worth adding more
89     return RandomOrderAndNonNull(
90         {pass_instances_->GetApplyIdSynonyms(),
91          pass_instances_->GetAddEquationInstructions()});
92   }
93   if (&pass == pass_instances_->GetAddFunctionCalls()) {
94     // - Called functions can be inlined
95     // - Irrelevant ids are created, so they can be replaced
96     return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions(),
97                                   pass_instances_->GetReplaceIrrelevantIds()});
98   }
99   if (&pass == pass_instances_->GetAddGlobalVariables()) {
100     // - New globals provide new possibilities for making access chains
101     // - We can load from and store to new globals
102     return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
103                                   pass_instances_->GetAddLoads(),
104                                   pass_instances_->GetAddStores()});
105   }
106   if (&pass == pass_instances_->GetAddImageSampleUnusedComponents()) {
107     // - This introduces an unused component whose id is irrelevant and can be
108     //   replaced
109     return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
110   }
111   if (&pass == pass_instances_->GetAddLoads()) {
112     // - Loads might end up with corresponding stores, so that pairs can be
113     //   replaced with memory copies
114     return RandomOrderAndNonNull(
115         {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
116   }
117   if (&pass == pass_instances_->GetAddLocalVariables()) {
118     // - New locals provide new possibilities for making access chains
119     // - We can load from and store to new locals
120     return RandomOrderAndNonNull({pass_instances_->GetAddAccessChains(),
121                                   pass_instances_->GetAddLoads(),
122                                   pass_instances_->GetAddStores()});
123   }
124   if (&pass == pass_instances_->GetAddLoopPreheaders()) {
125     // - The loop preheader provides more scope for duplicating regions and
126     //   outlining functions.
127     return RandomOrderAndNonNull(
128         {pass_instances_->GetDuplicateRegionsWithSelections(),
129          pass_instances_->GetOutlineFunctions(),
130          pass_instances_->GetWrapRegionsInSelections()});
131   }
132   if (&pass == pass_instances_->GetAddLoopsToCreateIntConstantSynonyms()) {
133     // - New synonyms can be applied
134     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
135   }
136   if (&pass == pass_instances_->GetAddOpPhiSynonyms()) {
137     // - New synonyms can be applied
138     // - If OpPhi synonyms are introduced for blocks with dead predecessors, the
139     //   values consumed from dead predecessors can be replaced
140     return RandomOrderAndNonNull(
141         {pass_instances_->GetApplyIdSynonyms(),
142          pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()});
143   }
144   if (&pass == pass_instances_->GetAddParameters()) {
145     // - We might be able to create interesting synonyms of new parameters.
146     // - This introduces irrelevant ids, which can be replaced
147     return RandomOrderAndNonNull({pass_instances_->GetAddSynonyms(),
148                                   pass_instances_->GetReplaceIrrelevantIds()});
149   }
150   if (&pass == pass_instances_->GetAddRelaxedDecorations()) {
151     // - No obvious follow-on passes
152     return {};
153   }
154   if (&pass == pass_instances_->GetAddStores()) {
155     // - Stores might end up with corresponding loads, so that pairs can be
156     //   replaced with memory copies
157     return RandomOrderAndNonNull(
158         {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
159   }
160   if (&pass == pass_instances_->GetAddSynonyms()) {
161     // - New synonyms can be applied
162     // - Synonym instructions use constants, which can be obfuscated
163     // - Synonym instructions use irrelevant ids, which can be replaced
164     // - Synonym instructions introduce addition/subtraction, which can be
165     //   replaced with carrying/extended versions
166     return RandomOrderAndNonNull(
167         {pass_instances_->GetApplyIdSynonyms(),
168          pass_instances_->GetObfuscateConstants(),
169          pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended(),
170          pass_instances_->GetReplaceIrrelevantIds()});
171   }
172   if (&pass == pass_instances_->GetAddVectorShuffleInstructions()) {
173     // - Vector shuffles create synonyms that can be applied
174     // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806) Extract
175     //    from composites.
176     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
177   }
178   if (&pass == pass_instances_->GetApplyIdSynonyms()) {
179     // - No obvious follow-on passes
180     return {};
181   }
182   if (&pass == pass_instances_->GetConstructComposites()) {
183     // - TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3806): Extract
184     //    from composites.
185     return RandomOrderAndNonNull({});
186   }
187   if (&pass == pass_instances_->GetCopyObjects()) {
188     // - Object copies create synonyms that can be applied
189     // - OpCopyObject can be replaced with a store/load pair
190     return RandomOrderAndNonNull(
191         {pass_instances_->GetApplyIdSynonyms(),
192          pass_instances_->GetReplaceCopyObjectsWithStoresLoads()});
193   }
194   if (&pass == pass_instances_->GetDonateModules()) {
195     // - New functions in the module can be called
196     // - Donated dead functions produce irrelevant ids, which can be replaced
197     // - Donated functions are good candidates for having their returns merged
198     // - Donated dead functions may allow branches to be replaced with exits
199     return RandomOrderAndNonNull(
200         {pass_instances_->GetAddFunctionCalls(),
201          pass_instances_->GetReplaceIrrelevantIds(),
202          pass_instances_->GetMergeFunctionReturns(),
203          pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()});
204   }
205   if (&pass == pass_instances_->GetDuplicateRegionsWithSelections()) {
206     // - Parts of duplicated regions can be outlined
207     return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
208   }
209   if (&pass == pass_instances_->GetExpandVectorReductions()) {
210     // - Adding OpAny and OpAll synonyms creates opportunities to apply synonyms
211     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
212   }
213   if (&pass == pass_instances_->GetFlattenConditionalBranches()) {
214     // - Parts of flattened selections can be outlined
215     // - The flattening transformation introduces constants and irrelevant ids
216     //   for enclosing hard-to-flatten operations; these can be obfuscated or
217     //   replaced
218     return RandomOrderAndNonNull({pass_instances_->GetObfuscateConstants(),
219                                   pass_instances_->GetOutlineFunctions(),
220                                   pass_instances_->GetReplaceIrrelevantIds()});
221   }
222   if (&pass == pass_instances_->GetInlineFunctions()) {
223     // - Parts of inlined functions can be outlined again
224     return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
225   }
226   if (&pass == pass_instances_->GetInvertComparisonOperators()) {
227     // - No obvious follow-on passes
228     return {};
229   }
230   if (&pass == pass_instances_->GetMakeVectorOperationsDynamic()) {
231     // - No obvious follow-on passes
232     return {};
233   }
234   if (&pass == pass_instances_->GetMergeBlocks()) {
235     // - Having merged some blocks it may be interesting to split them in a
236     //   different way
237     return RandomOrderAndNonNull({pass_instances_->GetSplitBlocks()});
238   }
239   if (&pass == pass_instances_->GetMergeFunctionReturns()) {
240     // - Functions without early returns are more likely to be able to be
241     //   inlined.
242     return RandomOrderAndNonNull({pass_instances_->GetInlineFunctions()});
243   }
244   if (&pass == pass_instances_->GetMutatePointers()) {
245     // - This creates irrelevant ids, which can be replaced
246     return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
247   }
248   if (&pass == pass_instances_->GetObfuscateConstants()) {
249     // - No obvious follow-on passes
250     return {};
251   }
252   if (&pass == pass_instances_->GetOutlineFunctions()) {
253     // - This creates more functions, which can be called
254     // - Inlining the function for the region that was outlined might also be
255     //   fruitful; it will be inlined in a different form
256     return RandomOrderAndNonNull({pass_instances_->GetAddFunctionCalls(),
257                                   pass_instances_->GetInlineFunctions()});
258   }
259   if (&pass == pass_instances_->GetPermuteBlocks()) {
260     // No obvious follow-on passes
261     return {};
262   }
263   if (&pass == pass_instances_->GetPermuteFunctionParameters()) {
264     // No obvious follow-on passes
265     return {};
266   }
267   if (&pass == pass_instances_->GetPermuteInstructions()) {
268     // No obvious follow-on passes
269     return {};
270   }
271   if (&pass == pass_instances_->GetPropagateInstructionsDown()) {
272     // - This fuzzer pass might create new synonyms that can later be applied.
273     // - This fuzzer pass might create irrelevant ids that can later be
274     //   replaced.
275     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
276                                   pass_instances_->GetReplaceIrrelevantIds()});
277   }
278   if (&pass == pass_instances_->GetPropagateInstructionsUp()) {
279     // No obvious follow-on passes
280     return {};
281   }
282   if (&pass == pass_instances_->GetPushIdsThroughVariables()) {
283     // - This pass creates synonyms, so it is worth applying them
284     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
285   }
286   if (&pass == pass_instances_->GetReplaceAddsSubsMulsWithCarryingExtended()) {
287     // No obvious follow-on passes
288     return {};
289   }
290   if (&pass == pass_instances_->GetReplaceBranchesFromDeadBlocksWithExits()) {
291     // - Changing a branch to OpReturnValue introduces an irrelevant id, which
292     //   can be replaced
293     return RandomOrderAndNonNull({pass_instances_->GetReplaceIrrelevantIds()});
294   }
295   if (&pass == pass_instances_->GetReplaceCopyMemoriesWithLoadsStores()) {
296     // No obvious follow-on passes
297     return {};
298   }
299   if (&pass == pass_instances_->GetReplaceCopyObjectsWithStoresLoads()) {
300     // - We may end up with load/store pairs that could be used to create memory
301     //   copies
302     return RandomOrderAndNonNull(
303         {pass_instances_->GetReplaceLoadsStoresWithCopyMemories()});
304   }
305   if (&pass == pass_instances_->GetReplaceIrrelevantIds()) {
306     // No obvious follow-on passes
307     return {};
308   }
309   if (&pass == pass_instances_->GetReplaceLinearAlgebraInstructions()) {
310     // No obvious follow-on passes
311     return {};
312   }
313   if (&pass == pass_instances_->GetReplaceLoadsStoresWithCopyMemories()) {
314     // No obvious follow-on passes
315     return {};
316   }
317   if (&pass == pass_instances_->GetReplaceOpPhiIdsFromDeadPredecessors()) {
318     // No obvious follow-on passes
319     return {};
320   }
321   if (&pass == pass_instances_->GetReplaceOpSelectsWithConditionalBranches()) {
322     // No obvious follow-on passes
323     return {};
324   }
325   if (&pass == pass_instances_->GetReplaceParameterWithGlobal()) {
326     // No obvious follow-on passes
327     return {};
328   }
329   if (&pass == pass_instances_->GetReplaceParamsWithStruct()) {
330     // No obvious follow-on passes
331     return {};
332   }
333   if (&pass == pass_instances_->GetSplitBlocks()) {
334     // - More blocks means more chances for adding dead breaks/continues, and
335     //   for adding dead blocks
336     return RandomOrderAndNonNull({pass_instances_->GetAddDeadBreaks(),
337                                   pass_instances_->GetAddDeadContinues(),
338                                   pass_instances_->GetAddDeadBlocks()});
339   }
340   if (&pass == pass_instances_->GetSwapBranchConditionalOperands()) {
341     // No obvious follow-on passes
342     return {};
343   }
344   if (&pass == pass_instances_->GetWrapRegionsInSelections()) {
345     // - This pass uses an irrelevant boolean constant - we can replace it with
346     //   something more interesting.
347     // - We can obfuscate that very constant as well.
348     // - We can flatten created selection construct.
349     return RandomOrderAndNonNull(
350         {pass_instances_->GetObfuscateConstants(),
351          pass_instances_->GetReplaceIrrelevantIds(),
352          pass_instances_->GetFlattenConditionalBranches()});
353   }
354   if (&pass == pass_instances_->GetWrapVectorSynonym()) {
355     // This transformation introduces synonym facts and irrelevant ids.
356     return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms(),
357                                   pass_instances_->GetReplaceIrrelevantIds()});
358   }
359 
360   assert(false && "Unreachable: every fuzzer pass should be dealt with.");
361   return {};
362 }
363 
RandomOrderAndNonNull(const std::vector<FuzzerPass * > & passes)364 std::vector<FuzzerPass*> RepeatedPassRecommenderStandard::RandomOrderAndNonNull(
365     const std::vector<FuzzerPass*>& passes) {
366   std::vector<uint32_t> indices(passes.size());
367   std::iota(indices.begin(), indices.end(), 0);
368   std::vector<FuzzerPass*> result;
369   while (!indices.empty()) {
370     FuzzerPass* maybe_pass =
371         passes[fuzzer_context_->RemoveAtRandomIndex(&indices)];
372     if (maybe_pass != nullptr &&
373         fuzzer_context_->ChoosePercentage(
374             fuzzer_context_
375                 ->GetChanceOfAcceptingRepeatedPassRecommendation())) {
376       result.push_back(maybe_pass);
377     }
378   }
379   return result;
380 }
381 
382 }  // namespace fuzz
383 }  // namespace spvtools
384