transformation_add_loop_preheader.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 "transformation_add_loop_preheader.h"
  15. #include "source/fuzz/fuzzer_util.h"
  16. #include "source/opt/instruction.h"
  17. namespace spvtools {
  18. namespace fuzz {
  19. TransformationAddLoopPreheader::TransformationAddLoopPreheader(
  20. protobufs::TransformationAddLoopPreheader message)
  21. : message_(std::move(message)) {}
  22. TransformationAddLoopPreheader::TransformationAddLoopPreheader(
  23. uint32_t loop_header_block, uint32_t fresh_id,
  24. std::vector<uint32_t> phi_id) {
  25. message_.set_loop_header_block(loop_header_block);
  26. message_.set_fresh_id(fresh_id);
  27. for (auto id : phi_id) {
  28. message_.add_phi_id(id);
  29. }
  30. }
  31. bool TransformationAddLoopPreheader::IsApplicable(
  32. opt::IRContext* ir_context,
  33. const TransformationContext& /* unused */) const {
  34. // |message_.loop_header_block()| must be the id of a loop header block.
  35. opt::BasicBlock* loop_header_block =
  36. fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
  37. if (!loop_header_block || !loop_header_block->IsLoopHeader()) {
  38. return false;
  39. }
  40. // The id for the preheader must actually be fresh.
  41. std::set<uint32_t> used_ids;
  42. if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.fresh_id(),
  43. ir_context, &used_ids)) {
  44. return false;
  45. }
  46. size_t num_predecessors =
  47. ir_context->cfg()->preds(message_.loop_header_block()).size();
  48. // The block must have at least 2 predecessors (the back-edge block and
  49. // another predecessor outside of the loop)
  50. if (num_predecessors < 2) {
  51. return false;
  52. }
  53. // If the block only has one predecessor outside of the loop (and thus 2 in
  54. // total), then no additional fresh ids are necessary.
  55. if (num_predecessors == 2) {
  56. return true;
  57. }
  58. // Count the number of OpPhi instructions.
  59. int32_t num_phi_insts = 0;
  60. loop_header_block->ForEachPhiInst(
  61. [&num_phi_insts](opt::Instruction* /* unused */) { num_phi_insts++; });
  62. // There must be enough fresh ids for the OpPhi instructions.
  63. if (num_phi_insts > message_.phi_id_size()) {
  64. return false;
  65. }
  66. // Check that the needed ids are fresh and distinct.
  67. for (int32_t i = 0; i < num_phi_insts; i++) {
  68. if (!CheckIdIsFreshAndNotUsedByThisTransformation(message_.phi_id(i),
  69. ir_context, &used_ids)) {
  70. return false;
  71. }
  72. }
  73. return true;
  74. }
  75. void TransformationAddLoopPreheader::Apply(
  76. opt::IRContext* ir_context,
  77. TransformationContext* /* transformation_context */) const {
  78. // Find the loop header.
  79. opt::BasicBlock* loop_header =
  80. fuzzerutil::MaybeFindBlock(ir_context, message_.loop_header_block());
  81. auto dominator_analysis =
  82. ir_context->GetDominatorAnalysis(loop_header->GetParent());
  83. uint32_t back_edge_block_id = 0;
  84. // Update the branching instructions of the out-of-loop predecessors of the
  85. // header. Set |back_edge_block_id| to be the id of the back-edge block.
  86. ir_context->get_def_use_mgr()->ForEachUse(
  87. loop_header->id(),
  88. [this, &ir_context, &dominator_analysis, &loop_header,
  89. &back_edge_block_id](opt::Instruction* use_inst, uint32_t use_index) {
  90. if (dominator_analysis->Dominates(loop_header->GetLabelInst(),
  91. use_inst)) {
  92. // If |use_inst| is a branch instruction dominated by the header, the
  93. // block containing it is the back-edge block.
  94. if (use_inst->IsBranch()) {
  95. assert(back_edge_block_id == 0 &&
  96. "There should only be one back-edge block");
  97. back_edge_block_id = ir_context->get_instr_block(use_inst)->id();
  98. }
  99. // References to the header inside the loop should not be updated
  100. return;
  101. }
  102. // If |use_inst| is not a branch or merge instruction, it should not be
  103. // changed.
  104. if (!use_inst->IsBranch() &&
  105. use_inst->opcode() != spv::Op::OpSelectionMerge &&
  106. use_inst->opcode() != spv::Op::OpLoopMerge) {
  107. return;
  108. }
  109. // Update the reference.
  110. use_inst->SetOperand(use_index, {message_.fresh_id()});
  111. });
  112. assert(back_edge_block_id && "The back-edge block should have been found");
  113. // Make a new block for the preheader.
  114. std::unique_ptr<opt::BasicBlock> preheader = MakeUnique<opt::BasicBlock>(
  115. std::unique_ptr<opt::Instruction>(new opt::Instruction(
  116. ir_context, spv::Op::OpLabel, 0, message_.fresh_id(), {})));
  117. uint32_t phi_ids_used = 0;
  118. // Update the OpPhi instructions and, if there is more than one out-of-loop
  119. // predecessor, add necessary OpPhi instructions so the preheader.
  120. loop_header->ForEachPhiInst([this, &ir_context, &preheader,
  121. &back_edge_block_id,
  122. &phi_ids_used](opt::Instruction* phi_inst) {
  123. // The loop header must have at least 2 incoming edges (the back edge, and
  124. // at least one from outside the loop).
  125. assert(phi_inst->NumInOperands() >= 4);
  126. if (phi_inst->NumInOperands() == 4) {
  127. // There is just one out-of-loop predecessor, so no additional
  128. // instructions in the preheader are necessary. The reference to the
  129. // original out-of-loop predecessor needs to be updated so that it refers
  130. // to the preheader.
  131. uint32_t index_of_out_of_loop_pred_id =
  132. phi_inst->GetInOperand(1).words[0] == back_edge_block_id ? 3 : 1;
  133. phi_inst->SetInOperand(index_of_out_of_loop_pred_id, {preheader->id()});
  134. } else {
  135. // There is more than one out-of-loop predecessor, so an OpPhi instruction
  136. // needs to be added to the preheader, and its value will depend on all
  137. // the current out-of-loop predecessors of the header.
  138. // Get the operand list and the value corresponding to the back-edge
  139. // block.
  140. std::vector<opt::Operand> preheader_in_operands;
  141. uint32_t back_edge_val = 0;
  142. for (uint32_t i = 0; i < phi_inst->NumInOperands(); i += 2) {
  143. // Only add operands if they don't refer to the back-edge block.
  144. if (phi_inst->GetInOperand(i + 1).words[0] == back_edge_block_id) {
  145. back_edge_val = phi_inst->GetInOperand(i).words[0];
  146. } else {
  147. preheader_in_operands.push_back(std::move(phi_inst->GetInOperand(i)));
  148. preheader_in_operands.push_back(
  149. std::move(phi_inst->GetInOperand(i + 1)));
  150. }
  151. }
  152. // Add the new instruction to the preheader.
  153. uint32_t fresh_phi_id = message_.phi_id(phi_ids_used++);
  154. // Update id bound.
  155. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_phi_id);
  156. preheader->AddInstruction(std::unique_ptr<opt::Instruction>(
  157. new opt::Instruction(ir_context, spv::Op::OpPhi, phi_inst->type_id(),
  158. fresh_phi_id, preheader_in_operands)));
  159. // Update the OpPhi instruction in the header so that it refers to the
  160. // back edge block and the preheader as the predecessors, and it uses the
  161. // newly-defined OpPhi in the preheader for the corresponding value.
  162. phi_inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {fresh_phi_id}},
  163. {SPV_OPERAND_TYPE_ID, {preheader->id()}},
  164. {SPV_OPERAND_TYPE_ID, {back_edge_val}},
  165. {SPV_OPERAND_TYPE_ID, {back_edge_block_id}}});
  166. }
  167. });
  168. // Update id bound.
  169. fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
  170. // Add an unconditional branch from the preheader to the header.
  171. preheader->AddInstruction(
  172. std::unique_ptr<opt::Instruction>(new opt::Instruction(
  173. ir_context, spv::Op::OpBranch, 0, 0,
  174. std::initializer_list<opt::Operand>{opt::Operand(
  175. spv_operand_type_t::SPV_OPERAND_TYPE_ID, {loop_header->id()})})));
  176. // Insert the preheader in the module.
  177. loop_header->GetParent()->InsertBasicBlockBefore(std::move(preheader),
  178. loop_header);
  179. // Invalidate analyses because the structure of the program changed.
  180. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
  181. }
  182. protobufs::Transformation TransformationAddLoopPreheader::ToMessage() const {
  183. protobufs::Transformation result;
  184. *result.mutable_add_loop_preheader() = message_;
  185. return result;
  186. }
  187. std::unordered_set<uint32_t> TransformationAddLoopPreheader::GetFreshIds()
  188. const {
  189. std::unordered_set<uint32_t> result = {message_.fresh_id()};
  190. for (auto id : message_.phi_id()) {
  191. result.insert(id);
  192. }
  193. return result;
  194. }
  195. } // namespace fuzz
  196. } // namespace spvtools