fuzzer_pass_construct_composites.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. // Copyright (c) 2019 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_construct_composites.h"
  15. #include <memory>
  16. #include "source/fuzz/fuzzer_util.h"
  17. #include "source/fuzz/transformation_composite_construct.h"
  18. namespace spvtools {
  19. namespace fuzz {
  20. FuzzerPassConstructComposites::FuzzerPassConstructComposites(
  21. opt::IRContext* ir_context, TransformationContext* transformation_context,
  22. FuzzerContext* fuzzer_context,
  23. protobufs::TransformationSequence* transformations)
  24. : FuzzerPass(ir_context, transformation_context, fuzzer_context,
  25. transformations) {}
  26. FuzzerPassConstructComposites::~FuzzerPassConstructComposites() = default;
  27. void FuzzerPassConstructComposites::Apply() {
  28. // Gather up the ids of all composite types, but skip block-/buffer
  29. // block-decorated struct types.
  30. std::vector<uint32_t> composite_type_ids;
  31. for (auto& inst : GetIRContext()->types_values()) {
  32. if (fuzzerutil::IsCompositeType(
  33. GetIRContext()->get_type_mgr()->GetType(inst.result_id())) &&
  34. !fuzzerutil::HasBlockOrBufferBlockDecoration(GetIRContext(),
  35. inst.result_id())) {
  36. composite_type_ids.push_back(inst.result_id());
  37. }
  38. }
  39. ForEachInstructionWithInstructionDescriptor(
  40. [this, &composite_type_ids](
  41. opt::Function* function, opt::BasicBlock* block,
  42. opt::BasicBlock::iterator inst_it,
  43. const protobufs::InstructionDescriptor& instruction_descriptor)
  44. -> void {
  45. // Check whether it is legitimate to insert a composite construction
  46. // before the instruction.
  47. if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(
  48. SpvOpCompositeConstruct, inst_it)) {
  49. return;
  50. }
  51. // Randomly decide whether to try inserting an object copy here.
  52. if (!GetFuzzerContext()->ChoosePercentage(
  53. GetFuzzerContext()->GetChanceOfConstructingComposite())) {
  54. return;
  55. }
  56. // For each instruction that is available at this program point (i.e. an
  57. // instruction that is global or whose definition strictly dominates the
  58. // program point) and suitable for making a synonym of, associate it
  59. // with the id of its result type.
  60. TypeIdToInstructions type_id_to_available_instructions;
  61. auto available_instructions = FindAvailableInstructions(
  62. function, block, inst_it,
  63. [this](opt::IRContext* ir_context, opt::Instruction* inst) {
  64. if (!inst->result_id() || !inst->type_id()) {
  65. return false;
  66. }
  67. // If the id is irrelevant, we can use it since it will not
  68. // participate in DataSynonym fact. Otherwise, we should be able
  69. // to produce a synonym out of the id.
  70. return GetTransformationContext()
  71. ->GetFactManager()
  72. ->IdIsIrrelevant(inst->result_id()) ||
  73. fuzzerutil::CanMakeSynonymOf(
  74. ir_context, *GetTransformationContext(), inst);
  75. });
  76. for (auto instruction : available_instructions) {
  77. RecordAvailableInstruction(instruction,
  78. &type_id_to_available_instructions);
  79. }
  80. // At this point, |composite_type_ids| captures all the composite types
  81. // we could try to create, while |type_id_to_available_instructions|
  82. // captures all the available result ids we might use, organized by
  83. // type.
  84. // Now we try to find a composite that we can construct. We might not
  85. // manage, if there is a paucity of available ingredients in the module
  86. // (e.g. if our only available composite was a boolean vector and we had
  87. // no instructions generating boolean result types available).
  88. //
  89. // If we succeed, |chosen_composite_type| will end up being non-zero,
  90. // and |constructor_arguments| will end up giving us result ids suitable
  91. // for constructing a composite of that type. Otherwise these variables
  92. // will remain 0 and null respectively.
  93. uint32_t chosen_composite_type = 0;
  94. std::vector<uint32_t> constructor_arguments;
  95. // Initially, all composite type ids are available for us to try. Keep
  96. // trying until we run out of options.
  97. auto composites_to_try_constructing = composite_type_ids;
  98. while (!composites_to_try_constructing.empty()) {
  99. // Remove a composite type from the composite types left for us to
  100. // try.
  101. auto next_composite_to_try_constructing =
  102. GetFuzzerContext()->RemoveAtRandomIndex(
  103. &composites_to_try_constructing);
  104. // Now try to construct a composite of this type, using an appropriate
  105. // helper method depending on the kind of composite type.
  106. auto composite_type_inst = GetIRContext()->get_def_use_mgr()->GetDef(
  107. next_composite_to_try_constructing);
  108. switch (composite_type_inst->opcode()) {
  109. case SpvOpTypeArray:
  110. constructor_arguments = FindComponentsToConstructArray(
  111. *composite_type_inst, type_id_to_available_instructions);
  112. break;
  113. case SpvOpTypeMatrix:
  114. constructor_arguments = FindComponentsToConstructMatrix(
  115. *composite_type_inst, type_id_to_available_instructions);
  116. break;
  117. case SpvOpTypeStruct:
  118. constructor_arguments = FindComponentsToConstructStruct(
  119. *composite_type_inst, type_id_to_available_instructions);
  120. break;
  121. case SpvOpTypeVector:
  122. constructor_arguments = FindComponentsToConstructVector(
  123. *composite_type_inst, type_id_to_available_instructions);
  124. break;
  125. default:
  126. assert(false &&
  127. "The space of possible composite types should be covered "
  128. "by the above cases.");
  129. break;
  130. }
  131. if (!constructor_arguments.empty()) {
  132. // We succeeded! Note the composite type we finally settled on, and
  133. // exit from the loop.
  134. chosen_composite_type = next_composite_to_try_constructing;
  135. break;
  136. }
  137. }
  138. if (!chosen_composite_type) {
  139. // We did not manage to make a composite; return 0 to indicate that no
  140. // instructions were added.
  141. assert(constructor_arguments.empty());
  142. return;
  143. }
  144. assert(!constructor_arguments.empty());
  145. // Make and apply a transformation.
  146. ApplyTransformation(TransformationCompositeConstruct(
  147. chosen_composite_type, constructor_arguments,
  148. instruction_descriptor, GetFuzzerContext()->GetFreshId()));
  149. });
  150. }
  151. void FuzzerPassConstructComposites::RecordAvailableInstruction(
  152. opt::Instruction* inst,
  153. TypeIdToInstructions* type_id_to_available_instructions) {
  154. if (type_id_to_available_instructions->count(inst->type_id()) == 0) {
  155. (*type_id_to_available_instructions)[inst->type_id()] = {};
  156. }
  157. type_id_to_available_instructions->at(inst->type_id()).push_back(inst);
  158. }
  159. std::vector<uint32_t>
  160. FuzzerPassConstructComposites::FindComponentsToConstructArray(
  161. const opt::Instruction& array_type_instruction,
  162. const TypeIdToInstructions& type_id_to_available_instructions) {
  163. assert(array_type_instruction.opcode() == SpvOpTypeArray &&
  164. "Precondition: instruction must be an array type.");
  165. // Get the element type for the array.
  166. auto element_type_id = array_type_instruction.GetSingleWordInOperand(0);
  167. // Get all instructions at our disposal that compute something of this element
  168. // type.
  169. auto available_instructions =
  170. type_id_to_available_instructions.find(element_type_id);
  171. if (available_instructions == type_id_to_available_instructions.cend()) {
  172. // If there are not any instructions available that compute the element type
  173. // of the array then we are not in a position to construct a composite with
  174. // this array type.
  175. return {};
  176. }
  177. uint32_t array_length =
  178. GetIRContext()
  179. ->get_def_use_mgr()
  180. ->GetDef(array_type_instruction.GetSingleWordInOperand(1))
  181. ->GetSingleWordInOperand(0);
  182. std::vector<uint32_t> result;
  183. for (uint32_t index = 0; index < array_length; index++) {
  184. result.push_back(available_instructions
  185. ->second[GetFuzzerContext()->RandomIndex(
  186. available_instructions->second)]
  187. ->result_id());
  188. }
  189. return result;
  190. }
  191. std::vector<uint32_t>
  192. FuzzerPassConstructComposites::FindComponentsToConstructMatrix(
  193. const opt::Instruction& matrix_type_instruction,
  194. const TypeIdToInstructions& type_id_to_available_instructions) {
  195. assert(matrix_type_instruction.opcode() == SpvOpTypeMatrix &&
  196. "Precondition: instruction must be a matrix type.");
  197. // Get the element type for the matrix.
  198. auto element_type_id = matrix_type_instruction.GetSingleWordInOperand(0);
  199. // Get all instructions at our disposal that compute something of this element
  200. // type.
  201. auto available_instructions =
  202. type_id_to_available_instructions.find(element_type_id);
  203. if (available_instructions == type_id_to_available_instructions.cend()) {
  204. // If there are not any instructions available that compute the element type
  205. // of the matrix then we are not in a position to construct a composite with
  206. // this matrix type.
  207. return {};
  208. }
  209. std::vector<uint32_t> result;
  210. for (uint32_t index = 0;
  211. index < matrix_type_instruction.GetSingleWordInOperand(1); index++) {
  212. result.push_back(available_instructions
  213. ->second[GetFuzzerContext()->RandomIndex(
  214. available_instructions->second)]
  215. ->result_id());
  216. }
  217. return result;
  218. }
  219. std::vector<uint32_t>
  220. FuzzerPassConstructComposites::FindComponentsToConstructStruct(
  221. const opt::Instruction& struct_type_instruction,
  222. const TypeIdToInstructions& type_id_to_available_instructions) {
  223. assert(struct_type_instruction.opcode() == SpvOpTypeStruct &&
  224. "Precondition: instruction must be a struct type.");
  225. std::vector<uint32_t> result;
  226. // Consider the type of each field of the struct.
  227. for (uint32_t in_operand_index = 0;
  228. in_operand_index < struct_type_instruction.NumInOperands();
  229. in_operand_index++) {
  230. auto element_type_id =
  231. struct_type_instruction.GetSingleWordInOperand(in_operand_index);
  232. // Find the instructions at our disposal that compute something of the field
  233. // type.
  234. auto available_instructions =
  235. type_id_to_available_instructions.find(element_type_id);
  236. if (available_instructions == type_id_to_available_instructions.cend()) {
  237. // If there are no such instructions, we cannot construct a composite of
  238. // this struct type.
  239. return {};
  240. }
  241. result.push_back(available_instructions
  242. ->second[GetFuzzerContext()->RandomIndex(
  243. available_instructions->second)]
  244. ->result_id());
  245. }
  246. return result;
  247. }
  248. std::vector<uint32_t>
  249. FuzzerPassConstructComposites::FindComponentsToConstructVector(
  250. const opt::Instruction& vector_type_instruction,
  251. const TypeIdToInstructions& type_id_to_available_instructions) {
  252. assert(vector_type_instruction.opcode() == SpvOpTypeVector &&
  253. "Precondition: instruction must be a vector type.");
  254. // Get details of the type underlying the vector, and the width of the vector,
  255. // for convenience.
  256. auto element_type_id = vector_type_instruction.GetSingleWordInOperand(0);
  257. auto element_type = GetIRContext()->get_type_mgr()->GetType(element_type_id);
  258. auto element_count = vector_type_instruction.GetSingleWordInOperand(1);
  259. // Collect a mapping, from type id to width, for scalar/vector types that are
  260. // smaller in width than |vector_type|, but that have the same underlying
  261. // type. For example, if |vector_type| is vec4, the mapping will be:
  262. // { float -> 1, vec2 -> 2, vec3 -> 3 }
  263. // The mapping will have missing entries if some of these types do not exist.
  264. std::map<uint32_t, uint32_t> smaller_vector_type_id_to_width;
  265. // Add the underlying type. This id must exist, in order for |vector_type| to
  266. // exist.
  267. smaller_vector_type_id_to_width[element_type_id] = 1;
  268. // Now add every vector type with width at least 2, and less than the width of
  269. // |vector_type|.
  270. for (uint32_t width = 2; width < element_count; width++) {
  271. opt::analysis::Vector smaller_vector_type(element_type, width);
  272. auto smaller_vector_type_id =
  273. GetIRContext()->get_type_mgr()->GetId(&smaller_vector_type);
  274. // We might find that there is no declared type of this smaller width.
  275. // For example, a module can declare vec4 without having declared vec2 or
  276. // vec3.
  277. if (smaller_vector_type_id) {
  278. smaller_vector_type_id_to_width[smaller_vector_type_id] = width;
  279. }
  280. }
  281. // Now we know the types that are available to us, we set about populating a
  282. // vector of the right length. We do this by deciding, with no order in mind,
  283. // which instructions we will use to populate the vector, and subsequently
  284. // randomly choosing an order. This is to avoid biasing construction of
  285. // vectors with smaller vectors to the left and scalars to the right. That is
  286. // a concern because, e.g. in the case of populating a vec4, if we populate
  287. // the constructor instructions left-to-right, we can always choose a vec3 to
  288. // construct the first three elements, but can only choose a vec3 to construct
  289. // the last three elements if we chose a float to construct the first element
  290. // (otherwise there will not be space left for a vec3).
  291. uint32_t vector_slots_used = 0;
  292. // The instructions we will use to construct the vector, in no particular
  293. // order at this stage.
  294. std::vector<opt::Instruction*> instructions_to_use;
  295. while (vector_slots_used < element_count) {
  296. std::vector<opt::Instruction*> instructions_to_choose_from;
  297. for (auto& entry : smaller_vector_type_id_to_width) {
  298. if (entry.second >
  299. std::min(element_count - 1, element_count - vector_slots_used)) {
  300. continue;
  301. }
  302. auto available_instructions =
  303. type_id_to_available_instructions.find(entry.first);
  304. if (available_instructions == type_id_to_available_instructions.cend()) {
  305. continue;
  306. }
  307. instructions_to_choose_from.insert(instructions_to_choose_from.end(),
  308. available_instructions->second.begin(),
  309. available_instructions->second.end());
  310. }
  311. if (instructions_to_choose_from.empty()) {
  312. // We may get unlucky and find that there are not any instructions to
  313. // choose from. In this case we give up constructing a composite of this
  314. // vector type. It might be that we could construct the composite in
  315. // another manner, so we could opt to retry a few times here, but it is
  316. // simpler to just give up on the basis that this will not happen
  317. // frequently.
  318. return {};
  319. }
  320. auto instruction_to_use =
  321. instructions_to_choose_from[GetFuzzerContext()->RandomIndex(
  322. instructions_to_choose_from)];
  323. instructions_to_use.push_back(instruction_to_use);
  324. auto chosen_type =
  325. GetIRContext()->get_type_mgr()->GetType(instruction_to_use->type_id());
  326. if (chosen_type->AsVector()) {
  327. assert(chosen_type->AsVector()->element_type() == element_type);
  328. assert(chosen_type->AsVector()->element_count() < element_count);
  329. assert(chosen_type->AsVector()->element_count() <=
  330. element_count - vector_slots_used);
  331. vector_slots_used += chosen_type->AsVector()->element_count();
  332. } else {
  333. assert(chosen_type == element_type);
  334. vector_slots_used += 1;
  335. }
  336. }
  337. assert(vector_slots_used == element_count);
  338. std::vector<uint32_t> result;
  339. std::vector<uint32_t> operands;
  340. while (!instructions_to_use.empty()) {
  341. auto index = GetFuzzerContext()->RandomIndex(instructions_to_use);
  342. result.push_back(instructions_to_use[index]->result_id());
  343. instructions_to_use.erase(instructions_to_use.begin() + index);
  344. }
  345. assert(result.size() > 1);
  346. return result;
  347. }
  348. } // namespace fuzz
  349. } // namespace spvtools