transformation_inline_function.cpp 14 KB

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