2
0

transformation_inline_function.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  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,
  31. const TransformationContext& transformation_context) const {
  32. // The values in the |message_.result_id_map| must be all fresh and all
  33. // distinct.
  34. const auto result_id_map =
  35. fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
  36. std::set<uint32_t> ids_used_by_this_transformation;
  37. for (auto& pair : result_id_map) {
  38. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  39. pair.second, ir_context, &ids_used_by_this_transformation)) {
  40. return false;
  41. }
  42. }
  43. // |function_call_instruction| must be suitable for inlining.
  44. auto* function_call_instruction =
  45. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  46. if (!IsSuitableForInlining(ir_context, function_call_instruction)) {
  47. return false;
  48. }
  49. // |function_call_instruction| must be the penultimate instruction in its
  50. // block and its block termination instruction must be an OpBranch. This
  51. // avoids the case where the penultimate instruction is an OpLoopMerge, which
  52. // would make the back-edge block not branch to the loop header.
  53. auto* function_call_instruction_block =
  54. ir_context->get_instr_block(function_call_instruction);
  55. if (function_call_instruction !=
  56. &*--function_call_instruction_block->tail() ||
  57. function_call_instruction_block->terminator()->opcode() != SpvOpBranch) {
  58. return false;
  59. }
  60. auto* called_function = fuzzerutil::FindFunction(
  61. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  62. for (auto& block : *called_function) {
  63. // Since the entry block label will not be inlined, only the remaining
  64. // labels must have a corresponding value in the map.
  65. if (&block != &*called_function->entry() &&
  66. !result_id_map.count(block.id()) &&
  67. !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
  68. return false;
  69. }
  70. // |result_id_map| must have an entry for every result id in the called
  71. // function.
  72. for (auto& instruction : block) {
  73. // If |instruction| has result id, then it must have a mapped id in
  74. // |result_id_map|.
  75. if (instruction.HasResultId() &&
  76. !result_id_map.count(instruction.result_id()) &&
  77. !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
  78. return false;
  79. }
  80. }
  81. }
  82. // |result_id_map| must not contain an entry for any parameter of the function
  83. // that is being inlined.
  84. bool found_entry_for_parameter = false;
  85. called_function->ForEachParam(
  86. [&result_id_map, &found_entry_for_parameter](opt::Instruction* param) {
  87. if (result_id_map.count(param->result_id())) {
  88. found_entry_for_parameter = true;
  89. }
  90. });
  91. return !found_entry_for_parameter;
  92. }
  93. void TransformationInlineFunction::Apply(
  94. opt::IRContext* ir_context,
  95. TransformationContext* transformation_context) const {
  96. auto* function_call_instruction =
  97. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  98. auto* caller_function =
  99. ir_context->get_instr_block(function_call_instruction)->GetParent();
  100. auto* called_function = fuzzerutil::FindFunction(
  101. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  102. std::map<uint32_t, uint32_t> result_id_map =
  103. fuzzerutil::RepeatedUInt32PairToMap(message_.result_id_map());
  104. // If there are gaps in the result id map, fill them using overflow ids.
  105. for (auto& block : *called_function) {
  106. if (&block != &*called_function->entry() &&
  107. !result_id_map.count(block.id())) {
  108. result_id_map.insert(
  109. {block.id(),
  110. transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
  111. }
  112. for (auto& instruction : block) {
  113. // If |instruction| has result id, then it must have a mapped id in
  114. // |result_id_map|.
  115. if (instruction.HasResultId() &&
  116. !result_id_map.count(instruction.result_id())) {
  117. result_id_map.insert({instruction.result_id(),
  118. transformation_context->GetOverflowIdSource()
  119. ->GetNextOverflowId()});
  120. }
  121. }
  122. }
  123. auto* successor_block = ir_context->cfg()->block(
  124. ir_context->get_instr_block(function_call_instruction)
  125. ->terminator()
  126. ->GetSingleWordInOperand(0));
  127. // Inline the |called_function| entry block.
  128. for (auto& entry_block_instruction : *called_function->entry()) {
  129. opt::Instruction* inlined_instruction;
  130. if (entry_block_instruction.opcode() == SpvOpVariable) {
  131. // All OpVariable instructions in a function must be in the first block
  132. // in the function.
  133. inlined_instruction = caller_function->begin()->begin()->InsertBefore(
  134. std::unique_ptr<opt::Instruction>(
  135. entry_block_instruction.Clone(ir_context)));
  136. } else {
  137. inlined_instruction = function_call_instruction->InsertBefore(
  138. std::unique_ptr<opt::Instruction>(
  139. entry_block_instruction.Clone(ir_context)));
  140. }
  141. AdaptInlinedInstruction(result_id_map, ir_context, inlined_instruction);
  142. }
  143. // If the function call's successor block contains OpPhi instructions that
  144. // refer to the block containing the call then these will need to be rewritten
  145. // to instead refer to the block associated with "returning" from the inlined
  146. // function, as this block will be the predecessor of what used to be the
  147. // function call's successor block. We look out for this block.
  148. uint32_t new_return_block_id = 0;
  149. // Inline the |called_function| non-entry blocks.
  150. for (auto& block : *called_function) {
  151. if (&block == &*called_function->entry()) {
  152. continue;
  153. }
  154. // Check whether this is the function's return block. Take note if it is,
  155. // so that OpPhi instructions in the successor of the original function call
  156. // block can be re-written.
  157. if (block.terminator()->IsReturn()) {
  158. assert(new_return_block_id == 0 &&
  159. "There should be only one return block.");
  160. new_return_block_id = result_id_map.at(block.id());
  161. }
  162. auto* cloned_block = block.Clone(ir_context);
  163. cloned_block = caller_function->InsertBasicBlockBefore(
  164. std::unique_ptr<opt::BasicBlock>(cloned_block), successor_block);
  165. cloned_block->GetLabel()->SetResultId(result_id_map.at(cloned_block->id()));
  166. fuzzerutil::UpdateModuleIdBound(ir_context, cloned_block->id());
  167. for (auto& inlined_instruction : *cloned_block) {
  168. AdaptInlinedInstruction(result_id_map, ir_context, &inlined_instruction);
  169. }
  170. }
  171. opt::BasicBlock* block_containing_function_call =
  172. ir_context->get_instr_block(function_call_instruction);
  173. assert(((new_return_block_id == 0) ==
  174. called_function->entry()->terminator()->IsReturn()) &&
  175. "We should have found a return block unless the function being "
  176. "inlined returns in its first block.");
  177. if (new_return_block_id != 0) {
  178. // Rewrite any OpPhi instructions in the successor block so that they refer
  179. // to the new return block instead of the block that originally contained
  180. // the function call.
  181. ir_context->get_def_use_mgr()->ForEachUse(
  182. block_containing_function_call->id(),
  183. [ir_context, new_return_block_id, successor_block](
  184. opt::Instruction* use_instruction, uint32_t operand_index) {
  185. if (use_instruction->opcode() == SpvOpPhi &&
  186. ir_context->get_instr_block(use_instruction) == successor_block) {
  187. use_instruction->SetOperand(operand_index, {new_return_block_id});
  188. }
  189. });
  190. }
  191. // Removes the function call instruction and its block termination instruction
  192. // from |caller_function|.
  193. ir_context->KillInst(block_containing_function_call->terminator());
  194. ir_context->KillInst(function_call_instruction);
  195. // Since the SPIR-V module has changed, no analyses must be validated.
  196. ir_context->InvalidateAnalysesExceptFor(
  197. opt::IRContext::Analysis::kAnalysisNone);
  198. }
  199. protobufs::Transformation TransformationInlineFunction::ToMessage() const {
  200. protobufs::Transformation result;
  201. *result.mutable_inline_function() = message_;
  202. return result;
  203. }
  204. bool TransformationInlineFunction::IsSuitableForInlining(
  205. opt::IRContext* ir_context, opt::Instruction* function_call_instruction) {
  206. // |function_call_instruction| must be defined and must be an OpFunctionCall
  207. // instruction.
  208. if (!function_call_instruction ||
  209. function_call_instruction->opcode() != SpvOpFunctionCall) {
  210. return false;
  211. }
  212. // If |function_call_instruction| return type is void, then
  213. // |function_call_instruction| must not have uses.
  214. if (ir_context->get_type_mgr()
  215. ->GetType(function_call_instruction->type_id())
  216. ->AsVoid() &&
  217. ir_context->get_def_use_mgr()->NumUses(function_call_instruction) != 0) {
  218. return false;
  219. }
  220. // |called_function| must not have an early return.
  221. auto called_function = fuzzerutil::FindFunction(
  222. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  223. if (called_function->HasEarlyReturn()) {
  224. return false;
  225. }
  226. // |called_function| must not use OpKill or OpUnreachable.
  227. if (fuzzerutil::FunctionContainsOpKillOrUnreachable(*called_function)) {
  228. return false;
  229. }
  230. return true;
  231. }
  232. void TransformationInlineFunction::AdaptInlinedInstruction(
  233. const std::map<uint32_t, uint32_t>& result_id_map,
  234. opt::IRContext* ir_context,
  235. opt::Instruction* instruction_to_be_inlined) const {
  236. auto* function_call_instruction =
  237. ir_context->get_def_use_mgr()->GetDef(message_.function_call_id());
  238. auto* called_function = fuzzerutil::FindFunction(
  239. ir_context, function_call_instruction->GetSingleWordInOperand(0));
  240. const auto* function_call_block =
  241. ir_context->get_instr_block(function_call_instruction);
  242. assert(function_call_block && "OpFunctionCall must belong to some block");
  243. // Replaces the operand ids with their mapped result ids.
  244. instruction_to_be_inlined->ForEachInId(
  245. [called_function, function_call_instruction, &result_id_map,
  246. function_call_block](uint32_t* id) {
  247. // We are not inlining the entry block of the |called_function|.
  248. //
  249. // We must check this condition first since we can't use the fresh id
  250. // from |result_id_map| even if it has one. This is because that fresh
  251. // id will never be added to the module since entry blocks are not
  252. // inlined.
  253. if (*id == called_function->entry()->id()) {
  254. *id = function_call_block->id();
  255. return;
  256. }
  257. // If |id| is mapped, then set it to its mapped value.
  258. if (result_id_map.count(*id)) {
  259. *id = result_id_map.at(*id);
  260. return;
  261. }
  262. uint32_t parameter_index = 0;
  263. called_function->ForEachParam(
  264. [id, function_call_instruction,
  265. &parameter_index](opt::Instruction* parameter_instruction) {
  266. // If the id is a function parameter, then set it to the
  267. // parameter value passed in the function call instruction.
  268. if (*id == parameter_instruction->result_id()) {
  269. // We do + 1 because the first in-operand for OpFunctionCall is
  270. // the function id that is being called.
  271. *id = function_call_instruction->GetSingleWordInOperand(
  272. parameter_index + 1);
  273. }
  274. parameter_index++;
  275. });
  276. });
  277. // If |instruction_to_be_inlined| has result id, then set it to its mapped
  278. // value.
  279. if (instruction_to_be_inlined->HasResultId()) {
  280. assert(result_id_map.count(instruction_to_be_inlined->result_id()) &&
  281. "Result id must be mapped to a fresh id.");
  282. instruction_to_be_inlined->SetResultId(
  283. result_id_map.at(instruction_to_be_inlined->result_id()));
  284. fuzzerutil::UpdateModuleIdBound(ir_context,
  285. instruction_to_be_inlined->result_id());
  286. }
  287. // The return instruction will be changed into an OpBranch to the basic
  288. // block that follows the block containing the function call.
  289. if (spvOpcodeIsReturn(instruction_to_be_inlined->opcode())) {
  290. uint32_t successor_block_id =
  291. ir_context->get_instr_block(function_call_instruction)
  292. ->terminator()
  293. ->GetSingleWordInOperand(0);
  294. switch (instruction_to_be_inlined->opcode()) {
  295. case SpvOpReturn:
  296. instruction_to_be_inlined->AddOperand(
  297. {SPV_OPERAND_TYPE_ID, {successor_block_id}});
  298. break;
  299. case SpvOpReturnValue: {
  300. instruction_to_be_inlined->InsertBefore(MakeUnique<opt::Instruction>(
  301. ir_context, SpvOpCopyObject, function_call_instruction->type_id(),
  302. function_call_instruction->result_id(),
  303. opt::Instruction::OperandList(
  304. {{SPV_OPERAND_TYPE_ID,
  305. {instruction_to_be_inlined->GetSingleWordOperand(0)}}})));
  306. instruction_to_be_inlined->SetInOperand(0, {successor_block_id});
  307. break;
  308. }
  309. default:
  310. break;
  311. }
  312. instruction_to_be_inlined->SetOpcode(SpvOpBranch);
  313. }
  314. }
  315. std::unordered_set<uint32_t> TransformationInlineFunction::GetFreshIds() const {
  316. std::unordered_set<uint32_t> result;
  317. for (auto& pair : message_.result_id_map()) {
  318. result.insert(pair.second());
  319. }
  320. return result;
  321. }
  322. } // namespace fuzz
  323. } // namespace spvtools