fuzzer_pass_add_opphi_synonyms.cpp 11 KB

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