fuzzer_pass_flatten_conditional_branches.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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_flatten_conditional_branches.h"
  15. #include "source/fuzz/comparator_deep_blocks_first.h"
  16. #include "source/fuzz/instruction_descriptor.h"
  17. #include "source/fuzz/transformation_flatten_conditional_branch.h"
  18. namespace spvtools {
  19. namespace fuzz {
  20. // A fuzzer pass that randomly selects conditional branches to flatten and
  21. // flattens them, if possible.
  22. FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches(
  23. opt::IRContext* ir_context, TransformationContext* transformation_context,
  24. FuzzerContext* fuzzer_context,
  25. protobufs::TransformationSequence* transformations,
  26. bool ignore_inapplicable_transformations)
  27. : FuzzerPass(ir_context, transformation_context, fuzzer_context,
  28. transformations, ignore_inapplicable_transformations) {}
  29. void FuzzerPassFlattenConditionalBranches::Apply() {
  30. for (auto& function : *GetIRContext()->module()) {
  31. // Get all the selection headers that we want to flatten. We need to collect
  32. // all of them first, because, since we are changing the structure of the
  33. // module, it's not safe to modify them while iterating.
  34. std::vector<opt::BasicBlock*> selection_headers;
  35. for (auto& block : function) {
  36. // Randomly decide whether to consider this block.
  37. if (!GetFuzzerContext()->ChoosePercentage(
  38. GetFuzzerContext()->GetChanceOfFlatteningConditionalBranch())) {
  39. continue;
  40. }
  41. // Only consider this block if it is the header of a conditional, with a
  42. // non-irrelevant condition.
  43. if (block.GetMergeInst() &&
  44. block.GetMergeInst()->opcode() == spv::Op::OpSelectionMerge &&
  45. block.terminator()->opcode() == spv::Op::OpBranchConditional &&
  46. !GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
  47. block.terminator()->GetSingleWordInOperand(0))) {
  48. selection_headers.emplace_back(&block);
  49. }
  50. }
  51. // Sort the headers so that those that are more deeply nested are considered
  52. // first, possibly enabling outer conditionals to be flattened.
  53. std::sort(selection_headers.begin(), selection_headers.end(),
  54. ComparatorDeepBlocksFirst(GetIRContext()));
  55. // Apply the transformation to the headers which can be flattened.
  56. for (auto header : selection_headers) {
  57. // Make a set to keep track of the instructions that need fresh ids.
  58. std::set<opt::Instruction*> instructions_that_need_ids;
  59. // Do not consider this header if the conditional cannot be flattened.
  60. if (!TransformationFlattenConditionalBranch::
  61. GetProblematicInstructionsIfConditionalCanBeFlattened(
  62. GetIRContext(), header, *GetTransformationContext(),
  63. &instructions_that_need_ids)) {
  64. continue;
  65. }
  66. uint32_t convergence_block_id =
  67. TransformationFlattenConditionalBranch::FindConvergenceBlock(
  68. GetIRContext(), *header);
  69. // If the SPIR-V version is restricted so that OpSelect can only work on
  70. // scalar, pointer and vector types then we cannot apply this
  71. // transformation to a header whose convergence block features OpPhi
  72. // instructions on different types, as we cannot convert such instructions
  73. // to OpSelect instructions.
  74. if (TransformationFlattenConditionalBranch::
  75. OpSelectArgumentsAreRestricted(GetIRContext())) {
  76. if (!GetIRContext()
  77. ->cfg()
  78. ->block(convergence_block_id)
  79. ->WhileEachPhiInst(
  80. [this](opt::Instruction* phi_instruction) -> bool {
  81. switch (GetIRContext()
  82. ->get_def_use_mgr()
  83. ->GetDef(phi_instruction->type_id())
  84. ->opcode()) {
  85. case spv::Op::OpTypeBool:
  86. case spv::Op::OpTypeInt:
  87. case spv::Op::OpTypeFloat:
  88. case spv::Op::OpTypePointer:
  89. case spv::Op::OpTypeVector:
  90. return true;
  91. default:
  92. return false;
  93. }
  94. })) {
  95. // An OpPhi is performed on a type not supported by OpSelect; we
  96. // cannot flatten this selection.
  97. continue;
  98. }
  99. }
  100. // If the construct's convergence block features OpPhi instructions with
  101. // vector result types then we may be *forced*, by the SPIR-V version, to
  102. // turn these into component-wise OpSelect instructions, or we might wish
  103. // to do so anyway. The following booleans capture whether we will opt
  104. // to use a component-wise select even if we don't have to.
  105. bool use_component_wise_2d_select_even_if_optional =
  106. GetFuzzerContext()->ChooseEven();
  107. bool use_component_wise_3d_select_even_if_optional =
  108. GetFuzzerContext()->ChooseEven();
  109. bool use_component_wise_4d_select_even_if_optional =
  110. GetFuzzerContext()->ChooseEven();
  111. // If we do need to perform any component-wise selections, we will need a
  112. // fresh id for a boolean vector representing the selection's condition
  113. // repeated N times, where N is the vector dimension.
  114. uint32_t fresh_id_for_bvec2_selector = 0;
  115. uint32_t fresh_id_for_bvec3_selector = 0;
  116. uint32_t fresh_id_for_bvec4_selector = 0;
  117. GetIRContext()
  118. ->cfg()
  119. ->block(convergence_block_id)
  120. ->ForEachPhiInst([this, &fresh_id_for_bvec2_selector,
  121. &fresh_id_for_bvec3_selector,
  122. &fresh_id_for_bvec4_selector,
  123. use_component_wise_2d_select_even_if_optional,
  124. use_component_wise_3d_select_even_if_optional,
  125. use_component_wise_4d_select_even_if_optional](
  126. opt::Instruction* phi_instruction) {
  127. opt::Instruction* type_instruction =
  128. GetIRContext()->get_def_use_mgr()->GetDef(
  129. phi_instruction->type_id());
  130. switch (type_instruction->opcode()) {
  131. case spv::Op::OpTypeVector: {
  132. uint32_t dimension =
  133. type_instruction->GetSingleWordInOperand(1);
  134. switch (dimension) {
  135. case 2:
  136. PrepareForOpPhiOnVectors(
  137. dimension,
  138. use_component_wise_2d_select_even_if_optional,
  139. &fresh_id_for_bvec2_selector);
  140. break;
  141. case 3:
  142. PrepareForOpPhiOnVectors(
  143. dimension,
  144. use_component_wise_3d_select_even_if_optional,
  145. &fresh_id_for_bvec3_selector);
  146. break;
  147. case 4:
  148. PrepareForOpPhiOnVectors(
  149. dimension,
  150. use_component_wise_4d_select_even_if_optional,
  151. &fresh_id_for_bvec4_selector);
  152. break;
  153. default:
  154. assert(false && "Invalid vector dimension.");
  155. }
  156. break;
  157. }
  158. default:
  159. break;
  160. }
  161. });
  162. // Some instructions will require to be enclosed inside conditionals
  163. // because they have side effects (for example, loads and stores). Some of
  164. // this have no result id, so we require instruction descriptors to
  165. // identify them. Each of them is associated with the necessary ids for it
  166. // via a SideEffectWrapperInfo message.
  167. std::vector<protobufs::SideEffectWrapperInfo> wrappers_info;
  168. for (auto instruction : instructions_that_need_ids) {
  169. protobufs::SideEffectWrapperInfo wrapper_info;
  170. *wrapper_info.mutable_instruction() =
  171. MakeInstructionDescriptor(GetIRContext(), instruction);
  172. wrapper_info.set_merge_block_id(GetFuzzerContext()->GetFreshId());
  173. wrapper_info.set_execute_block_id(GetFuzzerContext()->GetFreshId());
  174. // If the instruction has a non-void result id, we need to define more
  175. // fresh ids and provide an id of the suitable type whose value can be
  176. // copied in order to create a placeholder id.
  177. if (TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
  178. GetIRContext(), *instruction)) {
  179. wrapper_info.set_actual_result_id(GetFuzzerContext()->GetFreshId());
  180. wrapper_info.set_alternative_block_id(
  181. GetFuzzerContext()->GetFreshId());
  182. wrapper_info.set_placeholder_result_id(
  183. GetFuzzerContext()->GetFreshId());
  184. // The id will be a zero constant if the type allows it, and an
  185. // OpUndef otherwise. We want to avoid using OpUndef, if possible, to
  186. // avoid undefined behaviour in the module as much as possible.
  187. if (fuzzerutil::CanCreateConstant(GetIRContext(),
  188. instruction->type_id())) {
  189. wrapper_info.set_value_to_copy_id(
  190. FindOrCreateZeroConstant(instruction->type_id(), true));
  191. } else {
  192. wrapper_info.set_value_to_copy_id(
  193. FindOrCreateGlobalUndef(instruction->type_id()));
  194. }
  195. }
  196. wrappers_info.push_back(std::move(wrapper_info));
  197. }
  198. // Apply the transformation, evenly choosing whether to lay out the true
  199. // branch or the false branch first.
  200. ApplyTransformation(TransformationFlattenConditionalBranch(
  201. header->id(), GetFuzzerContext()->ChooseEven(),
  202. fresh_id_for_bvec2_selector, fresh_id_for_bvec3_selector,
  203. fresh_id_for_bvec4_selector, wrappers_info));
  204. }
  205. }
  206. }
  207. void FuzzerPassFlattenConditionalBranches::PrepareForOpPhiOnVectors(
  208. uint32_t vector_dimension, bool use_vector_select_if_optional,
  209. uint32_t* fresh_id_for_bvec_selector) {
  210. if (*fresh_id_for_bvec_selector != 0) {
  211. // We already have a fresh id for a component-wise OpSelect of this
  212. // dimension
  213. return;
  214. }
  215. if (TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
  216. GetIRContext()) ||
  217. use_vector_select_if_optional) {
  218. // We either have to, or have chosen to, perform a component-wise select, so
  219. // we ensure that the right boolean vector type is available, and grab a
  220. // fresh id.
  221. FindOrCreateVectorType(FindOrCreateBoolType(), vector_dimension);
  222. *fresh_id_for_bvec_selector = GetFuzzerContext()->GetFreshId();
  223. }
  224. }
  225. } // namespace fuzz
  226. } // namespace spvtools