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