• 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/fuzzer_pass_add_opphi_synonyms.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/transformation_add_opphi_synonym.h"
19 
20 namespace spvtools {
21 namespace fuzz {
22 
FuzzerPassAddOpPhiSynonyms(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations,bool ignore_inapplicable_transformations)23 FuzzerPassAddOpPhiSynonyms::FuzzerPassAddOpPhiSynonyms(
24     opt::IRContext* ir_context, TransformationContext* transformation_context,
25     FuzzerContext* fuzzer_context,
26     protobufs::TransformationSequence* transformations,
27     bool ignore_inapplicable_transformations)
28     : FuzzerPass(ir_context, transformation_context, fuzzer_context,
29                  transformations, ignore_inapplicable_transformations) {}
30 
Apply()31 void FuzzerPassAddOpPhiSynonyms::Apply() {
32   // Get a list of synonymous ids with the same type that can be used in the
33   // same OpPhi instruction.
34   auto equivalence_classes = GetIdEquivalenceClasses();
35 
36   // Make a list of references, to avoid copying sets unnecessarily.
37   std::vector<std::set<uint32_t>*> equivalence_class_pointers;
38   for (auto& set : equivalence_classes) {
39     equivalence_class_pointers.push_back(&set);
40   }
41 
42   // Keep a list of transformations to apply at the end.
43   std::vector<TransformationAddOpPhiSynonym> transformations_to_apply;
44 
45   for (auto& function : *GetIRContext()->module()) {
46     for (auto& block : function) {
47       // Randomly decide whether to consider this block.
48       if (!GetFuzzerContext()->ChoosePercentage(
49               GetFuzzerContext()->GetChanceOfAddingOpPhiSynonym())) {
50         continue;
51       }
52 
53       // The block must not be dead.
54       if (GetTransformationContext()->GetFactManager()->BlockIsDead(
55               block.id())) {
56         continue;
57       }
58 
59       // The block must have at least one predecessor.
60       size_t num_preds = GetIRContext()->cfg()->preds(block.id()).size();
61       if (num_preds == 0) {
62         continue;
63       }
64 
65       std::set<uint32_t>* chosen_equivalence_class = nullptr;
66 
67       if (num_preds > 1) {
68         // If the block has more than one predecessor, prioritise sets with at
69         // least 2 ids available at some predecessor.
70         chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
71             equivalence_class_pointers, block.id(), 2);
72       }
73 
74       // If a set was not already chosen, choose one with at least one available
75       // id.
76       if (!chosen_equivalence_class) {
77         chosen_equivalence_class = MaybeFindSuitableEquivalenceClassRandomly(
78             equivalence_class_pointers, block.id(), 1);
79       }
80 
81       // If no suitable set was found, we cannot apply the transformation to
82       // this block.
83       if (!chosen_equivalence_class) {
84         continue;
85       }
86 
87       // Initialise the map from predecessor labels to ids.
88       std::map<uint32_t, uint32_t> preds_to_ids;
89 
90       // Keep track of the ids used and of the id of a predecessor with at least
91       // two ids to choose from. This is to ensure that, if possible, at least
92       // two distinct ids will be used.
93       std::set<uint32_t> ids_chosen;
94       uint32_t pred_with_alternatives = 0;
95 
96       // Choose an id for each predecessor.
97       for (uint32_t pred_id : GetIRContext()->cfg()->preds(block.id())) {
98         auto suitable_ids = GetSuitableIds(*chosen_equivalence_class, pred_id);
99         assert(!suitable_ids.empty() &&
100                "We must be able to find at least one suitable id because the "
101                "equivalence class was chosen among suitable ones.");
102 
103         // If this predecessor has more than one id to choose from and it is the
104         // first one of this kind that we found, remember its id.
105         if (suitable_ids.size() > 1 && !pred_with_alternatives) {
106           pred_with_alternatives = pred_id;
107         }
108 
109         uint32_t chosen_id =
110             suitable_ids[GetFuzzerContext()->RandomIndex(suitable_ids)];
111 
112         // Add this id to the set of ids chosen.
113         ids_chosen.emplace(chosen_id);
114 
115         // Add the pair (predecessor, chosen id) to the map.
116         preds_to_ids[pred_id] = chosen_id;
117       }
118 
119       // If:
120       // - the block has more than one predecessor
121       // - at least one predecessor has more than one alternative
122       // - the same id has been chosen by all the predecessors
123       // then choose another one for the predecessor with more than one
124       // alternative.
125       if (num_preds > 1 && pred_with_alternatives != 0 &&
126           ids_chosen.size() == 1) {
127         auto suitable_ids =
128             GetSuitableIds(*chosen_equivalence_class, pred_with_alternatives);
129         uint32_t chosen_id =
130             GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
131         if (chosen_id == preds_to_ids[pred_with_alternatives]) {
132           chosen_id = GetFuzzerContext()->RemoveAtRandomIndex(&suitable_ids);
133         }
134 
135         preds_to_ids[pred_with_alternatives] = chosen_id;
136       }
137 
138       // Add the transformation to the list of transformations to apply.
139       transformations_to_apply.emplace_back(block.id(), preds_to_ids,
140                                             GetFuzzerContext()->GetFreshId());
141     }
142   }
143 
144   // Apply the transformations.
145   for (const auto& transformation : transformations_to_apply) {
146     ApplyTransformation(transformation);
147   }
148 }
149 
150 std::vector<std::set<uint32_t>>
GetIdEquivalenceClasses()151 FuzzerPassAddOpPhiSynonyms::GetIdEquivalenceClasses() {
152   std::vector<std::set<uint32_t>> id_equivalence_classes;
153 
154   // Keep track of all the ids that have already be assigned to a class.
155   std::set<uint32_t> already_in_a_class;
156 
157   for (const auto& pair : GetIRContext()->get_def_use_mgr()->id_to_defs()) {
158     // Exclude ids that have already been assigned to a class.
159     if (already_in_a_class.count(pair.first)) {
160       continue;
161     }
162 
163     // Exclude irrelevant ids.
164     if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
165             pair.first)) {
166       continue;
167     }
168 
169     // Exclude ids having a type that is not allowed by the transformation.
170     if (!TransformationAddOpPhiSynonym::CheckTypeIsAllowed(
171             GetIRContext(), pair.second->type_id())) {
172       continue;
173     }
174 
175     // Exclude OpFunction and OpUndef instructions, because:
176     // - OpFunction does not yield a value;
177     // - OpUndef yields an undefined value at each use, so it should never be a
178     //   synonym of another id.
179     if (pair.second->opcode() == SpvOpFunction ||
180         pair.second->opcode() == SpvOpUndef) {
181       continue;
182     }
183 
184     // We need a new equivalence class for this id.
185     std::set<uint32_t> new_equivalence_class;
186 
187     // Add this id to the class.
188     new_equivalence_class.emplace(pair.first);
189     already_in_a_class.emplace(pair.first);
190 
191     // Add all the synonyms with the same type to this class.
192     for (auto synonym :
193          GetTransformationContext()->GetFactManager()->GetSynonymsForId(
194              pair.first)) {
195       // The synonym must be a plain id - it cannot be an indexed access into a
196       // composite.
197       if (synonym->index_size() > 0) {
198         continue;
199       }
200 
201       // The synonym must not be irrelevant.
202       if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
203               synonym->object())) {
204         continue;
205       }
206 
207       auto synonym_def =
208           GetIRContext()->get_def_use_mgr()->GetDef(synonym->object());
209       // The synonym must exist and have the same type as the id we are
210       // considering.
211       if (!synonym_def || synonym_def->type_id() != pair.second->type_id()) {
212         continue;
213       }
214 
215       // We can add this synonym to the new equivalence class.
216       new_equivalence_class.emplace(synonym->object());
217       already_in_a_class.emplace(synonym->object());
218     }
219 
220     // Add the new equivalence class to the list of equivalence classes.
221     id_equivalence_classes.emplace_back(std::move(new_equivalence_class));
222   }
223 
224   return id_equivalence_classes;
225 }
226 
EquivalenceClassIsSuitableForBlock(const std::set<uint32_t> & equivalence_class,uint32_t block_id,uint32_t distinct_ids_required)227 bool FuzzerPassAddOpPhiSynonyms::EquivalenceClassIsSuitableForBlock(
228     const std::set<uint32_t>& equivalence_class, uint32_t block_id,
229     uint32_t distinct_ids_required) {
230   bool at_least_one_id_for_each_pred = true;
231 
232   // Keep a set of the suitable ids found.
233   std::set<uint32_t> suitable_ids_found;
234 
235   // Loop through all the predecessors of the block.
236   for (auto pred_id : GetIRContext()->cfg()->preds(block_id)) {
237     // Find the last instruction in the predecessor block.
238     auto last_instruction =
239         GetIRContext()->get_instr_block(pred_id)->terminator();
240 
241     // Initially assume that there is not a suitable id for this predecessor.
242     bool at_least_one_suitable_id_found = false;
243     for (uint32_t id : equivalence_class) {
244       if (fuzzerutil::IdIsAvailableBeforeInstruction(GetIRContext(),
245                                                      last_instruction, id)) {
246         // We have found a suitable id.
247         at_least_one_suitable_id_found = true;
248         suitable_ids_found.emplace(id);
249 
250         // If we have already found enough distinct suitable ids, we don't need
251         // to check the remaining ones for this predecessor.
252         if (suitable_ids_found.size() >= distinct_ids_required) {
253           break;
254         }
255       }
256     }
257     // If no suitable id was found for this predecessor, this equivalence class
258     // is not suitable and we don't need to check the other predecessors.
259     if (!at_least_one_suitable_id_found) {
260       at_least_one_id_for_each_pred = false;
261       break;
262     }
263   }
264 
265   // The equivalence class is suitable if at least one suitable id was found for
266   // each predecessor and we have found at least |distinct_ids_required|
267   // distinct suitable ids in general.
268   return at_least_one_id_for_each_pred &&
269          suitable_ids_found.size() >= distinct_ids_required;
270 }
271 
GetSuitableIds(const std::set<uint32_t> & ids,uint32_t pred_id)272 std::vector<uint32_t> FuzzerPassAddOpPhiSynonyms::GetSuitableIds(
273     const std::set<uint32_t>& ids, uint32_t pred_id) {
274   // Initialise an empty vector of suitable ids.
275   std::vector<uint32_t> suitable_ids;
276 
277   // Get the predecessor block.
278   auto predecessor = fuzzerutil::MaybeFindBlock(GetIRContext(), pred_id);
279 
280   // Loop through the ids to find the suitable ones.
281   for (uint32_t id : ids) {
282     if (fuzzerutil::IdIsAvailableBeforeInstruction(
283             GetIRContext(), predecessor->terminator(), id)) {
284       suitable_ids.push_back(id);
285     }
286   }
287 
288   return suitable_ids;
289 }
290 
291 std::set<uint32_t>*
MaybeFindSuitableEquivalenceClassRandomly(const std::vector<std::set<uint32_t> * > & candidates,uint32_t block_id,uint32_t distinct_ids_required)292 FuzzerPassAddOpPhiSynonyms::MaybeFindSuitableEquivalenceClassRandomly(
293     const std::vector<std::set<uint32_t>*>& candidates, uint32_t block_id,
294     uint32_t distinct_ids_required) {
295   auto remaining_candidates = candidates;
296   while (!remaining_candidates.empty()) {
297     // Choose one set randomly and return it if it is suitable.
298     auto chosen =
299         GetFuzzerContext()->RemoveAtRandomIndex(&remaining_candidates);
300     if (EquivalenceClassIsSuitableForBlock(*chosen, block_id,
301                                            distinct_ids_required)) {
302       return chosen;
303     }
304   }
305 
306   // No suitable sets were found.
307   return nullptr;
308 }
309 
310 }  // namespace fuzz
311 }  // namespace spvtools
312