transformation_inline_function.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. // Copyright (c) 2020 André Perez Maselco
  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/transformation_inline_function.h"
  15. #include "source/fuzz/fuzzer_util.h"
  16. #include "source/fuzz/instruction_descriptor.h"
  17. namespace spvtools {
  18. namespace fuzz {
  19. TransformationInlineFunction::TransformationInlineFunction(
  20. const spvtools::fuzz::protobufs::TransformationInlineFunction& message)
  21. : message_(message) {}
  22. TransformationInlineFunction::TransformationInlineFunction(
  23. uint32_t function_call_id,
  24. const std::map<uint32_t, uint32_t>& result_id_map) {
  25. message_.set_function_call_id(function_call_id);
  26. *message_.mutable_result_id_map() =
  27. fuzzerutil::MapToRepeatedUInt32Pair(result_id_map);
  28. }
  29. bool TransformationInlineFunction::IsApplicable(
  30. opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
  31. // The values in the |message_.result_id_map| must be all fresh and all
  32. // distinct.
  33. const auto result_id_map =
  34. fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
  35. std::set<uint32_t> ids_used_by_this_transformation;
  36. for (auto& pair : result_id_map) {
  37. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  38. pair.second, ir_context, &ids_used_by_this_transformation)) {
  39. return false;
  40. }
  41. }
  42. // |function_call_instruction| must be suitable for inlining.
  43. auto* function_call_instruction =
  44. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  45. if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
  46. return false;
  47. }
  48. // |function_call_instruction| must be the penultimate instruction in its
  49. // block and its block termination instruction must be an OpBranch. This
  50. // avoids the case where the penultimate instruction is an OpLoopMerge, which
  51. // would make the back-edge block not branch to the loop header.
  52. auto* function_call_instruction_block =
  53. ir_context->get_instr_block(function_call_instruction);
  54. if (function_call_instruction !=
  55. &*--function_call_instruction_block->tail() ||
  56. function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
  57. return false;
  58. }
  59. auto* called_function = fuzzerutil::FindFunction(
  60. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  61. for (auto& block : *called_function) {
  62. // Since the entry block label will not be inlined, only the remaining
  63. // labels must have a corresponding value in the map.
  64. if (&block != &*called_function->entry() &&
  65. !result_id_map.count(block.GetLabel()->result_id())) {
  66. return false;
  67. }
  68. // |result_id_map| must have an entry for every result id in the called
  69. // function.
  70. for (auto& instruction : block) {
  71. // If |instruction| has result id, then it must have a mapped id in
  72. // |result_id_map|.
  73. if (instruction.HasResultId() &&
  74. !result_id_map.count(instruction.result_id())) {
  75. return false;
  76. }
  77. }
  78. }
  79. // |result_id_map| must not contain an entry for any parameter of the function
  80. // that is being inlined.
  81. bool found_entry_for_parameter = false;
  82. called_function->ForEachParam(
  83. [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
  84. if (result_id_map.count(param->result_id())) {
  85. found_entry_for_parameter = true;
  86. }
  87. });
  88. return !found_entry_for_parameter;
  89. }
  90. void TransformationInlineFunction::Apply(
  91. opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
  92. auto* function_call_instruction =
  93. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  94. auto* caller_function =
  95. ir_context->get_instr_block(function_call_instruction)->GetParent();
  96. auto* called_function = fuzzerutil::FindFunction(
  97. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  98. const auto result_id_map =
  99. fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
  100. auto* successor_block = ir_context->cfg()->block(
  101. ir_context->get_instr_block(function_call_instruction)
  102. ->terminator()
  103. ->GetSingleWordInOperand(0));
  104. // Inline the |called_function| entry block.
  105. for (auto& entry_block_instruction : *called_function->entry()) {
  106. opt::Instruction* inlined_instruction = nullptr;
  107. if (entry_block_instruction.opcode() == SpvOpVariable) {
  108. // All OpVariable instructions in a function must be in the first block
  109. // in the function.
  110. inlined_instruction = caller_function->begin()->begin()->InsertBefore(
  111. MakeUnique<opt::Instruction>(entry_block_instruction));
  112. } else {
  113. inlined_instruction = function_call_instruction->InsertBefore(
  114. MakeUnique<opt::Instruction>(entry_block_instruction));
  115. }
  116. AdaptInlinedInstruction(ir_context, inlined_instruction);
  117. }
  118. // Inline the |called_function| non-entry blocks.
  119. for (auto& block : *called_function) {
  120. if (&block == &*called_function->entry()) {
  121. continue;
  122. }
  123. auto* cloned_block = block.Clone(ir_context);
  124. cloned_block = caller_function->InsertBasicBlockBefore(
  125. std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
  126. cloned_block->SetParent(caller_function);
  127. cloned_block->GetLabel()->SetResultId(
  128. result_id_map.at(cloned_block->GetLabel()->result_id()));
  129. fuzzerutil::UpdateModuleIdBound(ir_context,
  130. cloned_block->GetLabel()->result_id());
  131. for (auto& inlined_instruction : *cloned_block) {
  132. AdaptInlinedInstruction(ir_context, &inlined_instruction);
  133. }
  134. }
  135. // Removes the function call instruction and its block termination instruction
  136. // from |caller_function|.
  137. ir_context->KillInst(
  138. ir_context->get_instr_block(function_call_instruction)->terminator());
  139. ir_context->KillInst(function_call_instruction);
  140. // Since the SPIR-V module has changed, no analyses must be validated.
  141. ir_context->InvalidateAnalysesExceptFor(
  142. opt::IRContext::Analysis::kAnalysisNone);
  143. }
  144. protobufs::Transformation TransformationInlineFunction::ToMessage() const {
  145. protobufs::Transformation result;
  146. *result.mutable_inline_function() = message_;
  147. return result;
  148. }
  149. bool TransformationInlineFunction::IsSuitableForInlining(
  150. opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
  151. // |function_call_instruction| must be defined and must be an OpFunctionCall
  152. // instruction.
  153. if (!function_call_instruction ||
  154. function_call_instruction->opcode() != SpvOpFunctionCall) {
  155. return false;
  156. }
  157. // If |function_call_instruction| return type is void, then
  158. // |function_call_instruction| must not have uses.
  159. if (ir_context->get_type_mgr()
  160. ->GetType(function_call_instruction->type_id())
  161. ->AsVoid() &&
  162. ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
  163. return false;
  164. }
  165. // |called_function| must not have an early return.
  166. auto called_function = fuzzerutil::FindFunction(
  167. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  168. if (called_function->HasEarlyReturn()) {
  169. return false;
  170. }
  171. // |called_function| must not use OpKill or OpUnreachable.
  172. if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
  173. return false;
  174. }
  175. return true;
  176. }
  177. void TransformationInlineFunction::AdaptInlinedInstruction(
  178. opt::IRContext* ir_context,
  179. opt::Instruction* instruction_to_be_inlined) const {
  180. auto* function_call_instruction =
  181. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  182. auto* called_function = fuzzerutil::FindFunction(
  183. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  184. const auto result_id_map =
  185. fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
  186. // Replaces the operand ids with their mapped result ids.
  187. instruction_to_be_inlined->ForEachInId([called_function,
  188. function_call_instruction,
  189. &result_id_map](uint32_t* id) {
  190. // If |id| is mapped, then set it to its mapped value.
  191. if (result_id_map.count(*id)) {
  192. *id = result_id_map.at(*id);
  193. return;
  194. }
  195. uint32_t parameter_index = 0;
  196. called_function->ForEachParam(
  197. [id, function_call_instruction,
  198. &parameter_index](opt::Instruction* parameter_instruction) {
  199. // If the id is a function parameter, then set it to the
  200. // parameter value passed in the function call instruction.
  201. if (*id == parameter_instruction->result_id()) {
  202. // We do + 1 because the first in-operand for OpFunctionCall is
  203. // the function id that is being called.
  204. *id = function_call_instruction->GetSingleWordInOperand(
  205. parameter_index + 1);
  206. }
  207. parameter_index++;
  208. });
  209. });
  210. // If |instruction_to_be_inlined| has result id, then set it to its mapped
  211. // value.
  212. if (instruction_to_be_inlined->HasResultId()) {
  213. assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
  214. "Result id must be mapped to a fresh id.");
  215. instruction_to_be_inlined->SetResultId(
  216. result_id_map.at(instruction_to_be_inlined->result_id()));
  217. fuzzerutil::UpdateModuleIdBound(ir_context,
  218. instruction_to_be_inlined->result_id());
  219. }
  220. // The return instruction will be changed into an OpBranch to the basic
  221. // block that follows the block containing the function call.
  222. if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
  223. uint32_t successor_block_id =
  224. ir_context->get_instr_block(function_call_instruction)
  225. ->terminator()
  226. ->GetSingleWordInOperand(0);
  227. switch (instruction_to_be_inlined->opcode()) {
  228. case SpvOpReturn:
  229. instruction_to_be_inlined->AddOperand(
  230. {SPV_OPERAND_TYPE_ID, {successor_block_id}});
  231. break;
  232. case SpvOpReturnValue: {
  233. instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
  234. ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
  235. function_call_instruction->result_id(),
  236. opt::Instruction::OperandList(
  237. {{SPV_OPERAND_TYPE_ID,
  238. {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
  239. instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
  240. break;
  241. }
  242. default:
  243. break;
  244. }
  245. instruction_to_be_inlined->SetOpcode(SpvOpBranch);
  246. }
  247. }
  248. } // namespace fuzz
  249. } // namespace spvtools