transformation_outline_function.cpp 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945
  1. // Copyright (c) 2019 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/transformation_outline_function.h"
  15. #include <set>
  16. #include "source/fuzz/fuzzer_util.h"
  17. namespace spvtools {
  18. namespace fuzz {
  19. TransformationOutlineFunction::TransformationOutlineFunction(
  20. const spvtools::fuzz::protobufs::TransformationOutlineFunction& message)
  21. : message_(message) {}
  22. TransformationOutlineFunction::TransformationOutlineFunction(
  23. uint32_t entry_block, uint32_t exit_block,
  24. uint32_t new_function_struct_return_type_id, uint32_t new_function_type_id,
  25. uint32_t new_function_id, uint32_t new_function_region_entry_block,
  26. uint32_t new_caller_result_id, uint32_t new_callee_result_id,
  27. std::map<uint32_t, uint32_t>&& input_id_to_fresh_id,
  28. std::map<uint32_t, uint32_t>&& output_id_to_fresh_id) {
  29. message_.set_entry_block(entry_block);
  30. message_.set_exit_block(exit_block);
  31. message_.set_new_function_struct_return_type_id(
  32. new_function_struct_return_type_id);
  33. message_.set_new_function_type_id(new_function_type_id);
  34. message_.set_new_function_id(new_function_id);
  35. message_.set_new_function_region_entry_block(new_function_region_entry_block);
  36. message_.set_new_caller_result_id(new_caller_result_id);
  37. message_.set_new_callee_result_id(new_callee_result_id);
  38. *message_.mutable_input_id_to_fresh_id() =
  39. fuzzerutil::MapToRepeatedUInt32Pair(input_id_to_fresh_id);
  40. *message_.mutable_output_id_to_fresh_id() =
  41. fuzzerutil::MapToRepeatedUInt32Pair(output_id_to_fresh_id);
  42. }
  43. bool TransformationOutlineFunction::IsApplicable(
  44. opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
  45. std::set<uint32_t> ids_used_by_this_transformation;
  46. // The various new ids used by the transformation must be fresh and distinct.
  47. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  48. message_.new_function_struct_return_type_id(), ir_context,
  49. &ids_used_by_this_transformation)) {
  50. return false;
  51. }
  52. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  53. message_.new_function_type_id(), ir_context,
  54. &ids_used_by_this_transformation)) {
  55. return false;
  56. }
  57. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  58. message_.new_function_id(), ir_context,
  59. &ids_used_by_this_transformation)) {
  60. return false;
  61. }
  62. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  63. message_.new_function_region_entry_block(), ir_context,
  64. &ids_used_by_this_transformation)) {
  65. return false;
  66. }
  67. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  68. message_.new_caller_result_id(), ir_context,
  69. &ids_used_by_this_transformation)) {
  70. return false;
  71. }
  72. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  73. message_.new_callee_result_id(), ir_context,
  74. &ids_used_by_this_transformation)) {
  75. return false;
  76. }
  77. for (auto& pair : message_.input_id_to_fresh_id()) {
  78. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  79. pair.second(), ir_context, &ids_used_by_this_transformation)) {
  80. return false;
  81. }
  82. }
  83. for (auto& pair : message_.output_id_to_fresh_id()) {
  84. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  85. pair.second(), ir_context, &ids_used_by_this_transformation)) {
  86. return false;
  87. }
  88. }
  89. // The entry and exit block ids must indeed refer to blocks.
  90. for (auto block_id : {message_.entry_block(), message_.exit_block()}) {
  91. auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
  92. if (!block_label || block_label->opcode() != SpvOpLabel) {
  93. return false;
  94. }
  95. }
  96. auto entry_block = ir_context->cfg()->block(message_.entry_block());
  97. auto exit_block = ir_context->cfg()->block(message_.exit_block());
  98. // The entry block cannot start with OpVariable - this would mean that
  99. // outlining would remove a variable from the function containing the region
  100. // being outlined.
  101. if (entry_block->begin()->opcode() == SpvOpVariable) {
  102. return false;
  103. }
  104. // For simplicity, we do not allow the entry block to be a loop header.
  105. if (entry_block->GetLoopMergeInst()) {
  106. return false;
  107. }
  108. // For simplicity, we do not allow the exit block to be a merge block or
  109. // continue target.
  110. if (fuzzerutil::IsMergeOrContinue(ir_context, exit_block->id())) {
  111. return false;
  112. }
  113. // The entry block cannot start with OpPhi. This is to keep the
  114. // transformation logic simple. (Another transformation to split the OpPhis
  115. // from a block could be applied to avoid this scenario.)
  116. if (entry_block->begin()->opcode() == SpvOpPhi) {
  117. return false;
  118. }
  119. // The block must be in the same function.
  120. if (entry_block->GetParent() != exit_block->GetParent()) {
  121. return false;
  122. }
  123. // The entry block must dominate the exit block.
  124. auto dominator_analysis =
  125. ir_context->GetDominatorAnalysis(entry_block->GetParent());
  126. if (!dominator_analysis->Dominates(entry_block, exit_block)) {
  127. return false;
  128. }
  129. // The exit block must post-dominate the entry block.
  130. auto postdominator_analysis =
  131. ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
  132. if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
  133. return false;
  134. }
  135. // Find all the blocks dominated by |message_.entry_block| and post-dominated
  136. // by |message_.exit_block|.
  137. auto region_set = GetRegionBlocks(
  138. ir_context,
  139. entry_block = ir_context->cfg()->block(message_.entry_block()),
  140. exit_block = ir_context->cfg()->block(message_.exit_block()));
  141. // Check whether |region_set| really is a single-entry single-exit region, and
  142. // also check whether structured control flow constructs and their merge
  143. // and continue constructs are either wholly in or wholly out of the region -
  144. // e.g. avoid the situation where the region contains the head of a loop but
  145. // not the loop's continue construct.
  146. //
  147. // This is achieved by going through every block in the function that contains
  148. // the region.
  149. for (auto& block : *entry_block->GetParent()) {
  150. if (&block == exit_block) {
  151. // It is OK (and typically expected) for the exit block of the region to
  152. // have successors outside the region.
  153. //
  154. // It is also OK for the exit block to head a selection construct: the
  155. // block containing the call to the outlined function will end up heading
  156. // this construct if outlining takes place. However, it is not OK for
  157. // the exit block to head a loop construct.
  158. if (block.GetLoopMergeInst()) {
  159. return false;
  160. }
  161. continue;
  162. }
  163. if (region_set.count(&block) != 0) {
  164. // The block is in the region and is not the region's exit block. Let's
  165. // see whether all of the block's successors are in the region. If they
  166. // are not, the region is not single-entry single-exit.
  167. bool all_successors_in_region = true;
  168. block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
  169. &region_set](uint32_t successor) -> bool {
  170. if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
  171. all_successors_in_region = false;
  172. return false;
  173. }
  174. return true;
  175. });
  176. if (!all_successors_in_region) {
  177. return false;
  178. }
  179. }
  180. if (auto merge = block.GetMergeInst()) {
  181. // The block is a loop or selection header -- the header and its
  182. // associated merge block had better both be in the region or both be
  183. // outside the region.
  184. auto merge_block =
  185. ir_context->cfg()->block(merge->GetSingleWordOperand(0));
  186. if (region_set.count(&block) != region_set.count(merge_block)) {
  187. return false;
  188. }
  189. }
  190. if (auto loop_merge = block.GetLoopMergeInst()) {
  191. // Similar to the above, but for the continue target of a loop.
  192. auto continue_target =
  193. ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
  194. if (continue_target != exit_block &&
  195. region_set.count(&block) != region_set.count(continue_target)) {
  196. return false;
  197. }
  198. }
  199. }
  200. // For each region input id, i.e. every id defined outside the region but
  201. // used inside the region, ...
  202. auto input_id_to_fresh_id_map =
  203. fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
  204. for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
  205. // There needs to be a corresponding fresh id to be used as a function
  206. // parameter.
  207. if (input_id_to_fresh_id_map.count(id) == 0) {
  208. return false;
  209. }
  210. // Furthermore, if the input id has pointer type it must be an OpVariable
  211. // or OpFunctionParameter.
  212. auto input_id_inst = ir_context->get_def_use_mgr()->GetDef(id);
  213. if (ir_context->get_def_use_mgr()
  214. ->GetDef(input_id_inst->type_id())
  215. ->opcode() == SpvOpTypePointer) {
  216. switch (input_id_inst->opcode()) {
  217. case SpvOpFunctionParameter:
  218. case SpvOpVariable:
  219. // These are OK.
  220. break;
  221. default:
  222. // Anything else is not OK.
  223. return false;
  224. }
  225. }
  226. }
  227. // For each region output id -- i.e. every id defined inside the region but
  228. // used outside the region, ...
  229. auto output_id_to_fresh_id_map =
  230. fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
  231. for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
  232. if (
  233. // ... there needs to be a corresponding fresh id that can hold the
  234. // value for this id computed in the outlined function, and ...
  235. output_id_to_fresh_id_map.count(id) == 0
  236. // ... the output id must not have pointer type (to avoid creating a
  237. // struct with pointer members to pass data out of the outlined
  238. // function)
  239. || ir_context->get_def_use_mgr()
  240. ->GetDef(fuzzerutil::GetTypeId(ir_context, id))
  241. ->opcode() == SpvOpTypePointer) {
  242. return false;
  243. }
  244. }
  245. return true;
  246. }
  247. void TransformationOutlineFunction::Apply(
  248. opt::IRContext* ir_context,
  249. TransformationContext* transformation_context) const {
  250. // The entry block for the region before outlining.
  251. auto original_region_entry_block =
  252. ir_context->cfg()->block(message_.entry_block());
  253. // The exit block for the region before outlining.
  254. auto original_region_exit_block =
  255. ir_context->cfg()->block(message_.exit_block());
  256. // The single-entry single-exit region defined by |message_.entry_block| and
  257. // |message_.exit_block|.
  258. std::set<opt::BasicBlock*> region_blocks = GetRegionBlocks(
  259. ir_context, original_region_entry_block, original_region_exit_block);
  260. // Input and output ids for the region being outlined.
  261. std::vector<uint32_t> region_input_ids =
  262. GetRegionInputIds(ir_context, region_blocks, original_region_exit_block);
  263. std::vector<uint32_t> region_output_ids =
  264. GetRegionOutputIds(ir_context, region_blocks, original_region_exit_block);
  265. // Maps from input and output ids to fresh ids.
  266. auto input_id_to_fresh_id_map =
  267. fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
  268. auto output_id_to_fresh_id_map =
  269. fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
  270. UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
  271. output_id_to_fresh_id_map);
  272. // Construct a map that associates each output id with its type id.
  273. std::map<uint32_t, uint32_t> output_id_to_type_id;
  274. for (uint32_t output_id : region_output_ids) {
  275. output_id_to_type_id[output_id] =
  276. ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
  277. }
  278. // The region will be collapsed to a single block that calls a function
  279. // containing the outlined region. This block needs to end with whatever
  280. // the exit block of the region ended with before outlining. We thus clone
  281. // the terminator of the region's exit block, and the merge instruction for
  282. // the block if there is one, so that we can append them to the end of the
  283. // collapsed block later.
  284. std::unique_ptr<opt::Instruction> cloned_exit_block_terminator =
  285. std::unique_ptr<opt::Instruction>(
  286. original_region_exit_block->terminator()->Clone(ir_context));
  287. std::unique_ptr<opt::Instruction> cloned_exit_block_merge =
  288. original_region_exit_block->GetMergeInst()
  289. ? std::unique_ptr<opt::Instruction>(
  290. original_region_exit_block->GetMergeInst()->Clone(ir_context))
  291. : nullptr;
  292. // Make a function prototype for the outlined function, which involves
  293. // figuring out its required type.
  294. std::unique_ptr<opt::Function> outlined_function = PrepareFunctionPrototype(
  295. region_input_ids, region_output_ids, input_id_to_fresh_id_map, ir_context,
  296. transformation_context);
  297. // If the original function was livesafe, the new function should also be
  298. // livesafe.
  299. if (transformation_context->GetFactManager()->FunctionIsLivesafe(
  300. original_region_entry_block->GetParent()->result_id())) {
  301. transformation_context->GetFactManager()->AddFactFunctionIsLivesafe(
  302. message_.new_function_id());
  303. }
  304. // Adapt the region to be outlined so that its input ids are replaced with the
  305. // ids of the outlined function's input parameters, and so that output ids
  306. // are similarly remapped.
  307. RemapInputAndOutputIdsInRegion(
  308. ir_context, *original_region_exit_block, region_blocks, region_input_ids,
  309. region_output_ids, input_id_to_fresh_id_map, output_id_to_fresh_id_map);
  310. // Fill out the body of the outlined function according to the region that is
  311. // being outlined.
  312. PopulateOutlinedFunction(
  313. *original_region_entry_block, *original_region_exit_block, region_blocks,
  314. region_output_ids, output_id_to_fresh_id_map, ir_context,
  315. outlined_function.get(), transformation_context);
  316. // Collapse the region that has been outlined into a function down to a single
  317. // block that calls said function.
  318. ShrinkOriginalRegion(
  319. ir_context, region_blocks, region_input_ids, region_output_ids,
  320. output_id_to_type_id, outlined_function->type_id(),
  321. std::move(cloned_exit_block_merge),
  322. std::move(cloned_exit_block_terminator), original_region_entry_block);
  323. // Add the outlined function to the module.
  324. ir_context->module()->AddFunction(std::move(outlined_function));
  325. // Major surgery has been conducted on the module, so invalidate all analyses.
  326. ir_context->InvalidateAnalysesExceptFor(
  327. opt::IRContext::Analysis::kAnalysisNone);
  328. }
  329. protobufs::Transformation TransformationOutlineFunction::ToMessage() const {
  330. protobufs::Transformation result;
  331. *result.mutable_outline_function() = message_;
  332. return result;
  333. }
  334. std::vector<uint32_t> TransformationOutlineFunction::GetRegionInputIds(
  335. opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
  336. opt::BasicBlock* region_exit_block) {
  337. std::vector<uint32_t> result;
  338. auto enclosing_function = region_exit_block->GetParent();
  339. // Consider each parameter of the function containing the region.
  340. enclosing_function->ForEachParam(
  341. [ir_context, &region_set, &result](opt::Instruction* function_parameter) {
  342. // Consider every use of the parameter.
  343. ir_context->get_def_use_mgr()->WhileEachUse(
  344. function_parameter,
  345. [ir_context, function_parameter, &region_set, &result](
  346. opt::Instruction* use, uint32_t /*unused*/) {
  347. // Get the block, if any, in which the parameter is used.
  348. auto use_block = ir_context->get_instr_block(use);
  349. // If the use is in a block that lies within the region, the
  350. // parameter is an input id for the region.
  351. if (use_block && region_set.count(use_block) != 0) {
  352. result.push_back(function_parameter->result_id());
  353. return false;
  354. }
  355. return true;
  356. });
  357. });
  358. // Consider all definitions in the function that might turn out to be input
  359. // ids.
  360. for (auto& block : *enclosing_function) {
  361. std::vector<opt::Instruction*> candidate_input_ids_for_block;
  362. if (region_set.count(&block) == 0) {
  363. // All instructions in blocks outside the region are candidate's for
  364. // generating input ids.
  365. for (auto& inst : block) {
  366. candidate_input_ids_for_block.push_back(&inst);
  367. }
  368. } else {
  369. // Blocks in the region cannot generate input ids.
  370. continue;
  371. }
  372. // Consider each candidate input id to check whether it is used in the
  373. // region.
  374. for (auto& inst : candidate_input_ids_for_block) {
  375. ir_context->get_def_use_mgr()->WhileEachUse(
  376. inst,
  377. [ir_context, &inst, region_exit_block, &region_set, &result](
  378. opt::Instruction* use, uint32_t /*unused*/) -> bool {
  379. // Find the block in which this id use occurs, recording the id as
  380. // an input id if the block is outside the region, with some
  381. // exceptions detailed below.
  382. auto use_block = ir_context->get_instr_block(use);
  383. if (!use_block) {
  384. // There might be no containing block, e.g. if the use is in a
  385. // decoration.
  386. return true;
  387. }
  388. if (region_set.count(use_block) == 0) {
  389. // The use is not in the region: this does not make it an input
  390. // id.
  391. return true;
  392. }
  393. if (use_block == region_exit_block && use->IsBlockTerminator()) {
  394. // We do not regard uses in the exit block terminator as input
  395. // ids, as this terminator does not get outlined.
  396. return true;
  397. }
  398. result.push_back(inst->result_id());
  399. return false;
  400. });
  401. }
  402. }
  403. return result;
  404. }
  405. std::vector<uint32_t> TransformationOutlineFunction::GetRegionOutputIds(
  406. opt::IRContext* ir_context, const std::set<opt::BasicBlock*>& region_set,
  407. opt::BasicBlock* region_exit_block) {
  408. std::vector<uint32_t> result;
  409. // Consider each block in the function containing the region.
  410. for (auto& block : *region_exit_block->GetParent()) {
  411. if (region_set.count(&block) == 0) {
  412. // Skip blocks that are not in the region.
  413. continue;
  414. }
  415. // Consider each use of each instruction defined in the block.
  416. for (auto& inst : block) {
  417. ir_context->get_def_use_mgr()->WhileEachUse(
  418. &inst,
  419. [&region_set, ir_context, &inst, region_exit_block, &result](
  420. opt::Instruction* use, uint32_t /*unused*/) -> bool {
  421. // Find the block in which this id use occurs, recording the id as
  422. // an output id if the block is outside the region, with some
  423. // exceptions detailed below.
  424. auto use_block = ir_context->get_instr_block(use);
  425. if (!use_block) {
  426. // There might be no containing block, e.g. if the use is in a
  427. // decoration.
  428. return true;
  429. }
  430. if (region_set.count(use_block) != 0) {
  431. // The use is in the region.
  432. if (use_block != region_exit_block || !use->IsBlockTerminator()) {
  433. // Furthermore, the use is not in the terminator of the region's
  434. // exit block.
  435. return true;
  436. }
  437. }
  438. result.push_back(inst.result_id());
  439. return false;
  440. });
  441. }
  442. }
  443. return result;
  444. }
  445. std::set<opt::BasicBlock*> TransformationOutlineFunction::GetRegionBlocks(
  446. opt::IRContext* ir_context, opt::BasicBlock* entry_block,
  447. opt::BasicBlock* exit_block) {
  448. auto enclosing_function = entry_block->GetParent();
  449. auto dominator_analysis =
  450. ir_context->GetDominatorAnalysis(enclosing_function);
  451. auto postdominator_analysis =
  452. ir_context->GetPostDominatorAnalysis(enclosing_function);
  453. std::set<opt::BasicBlock*> result;
  454. for (auto& block : *enclosing_function) {
  455. if (dominator_analysis->Dominates(entry_block, &block) &&
  456. postdominator_analysis->Dominates(exit_block, &block)) {
  457. result.insert(&block);
  458. }
  459. }
  460. return result;
  461. }
  462. std::unique_ptr<opt::Function>
  463. TransformationOutlineFunction::PrepareFunctionPrototype(
  464. const std::vector<uint32_t>& region_input_ids,
  465. const std::vector<uint32_t>& region_output_ids,
  466. const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
  467. opt::IRContext* ir_context,
  468. TransformationContext* transformation_context) const {
  469. uint32_t return_type_id = 0;
  470. uint32_t function_type_id = 0;
  471. // First, try to find an existing function type that is suitable. This is
  472. // only possible if the region generates no output ids; if it generates output
  473. // ids we are going to make a new struct for those, and since that struct does
  474. // not exist there cannot already be a function type with this struct as its
  475. // return type.
  476. if (region_output_ids.empty()) {
  477. std::vector<uint32_t> return_and_parameter_types;
  478. opt::analysis::Void void_type;
  479. return_type_id = ir_context->get_type_mgr()->GetId(&void_type);
  480. return_and_parameter_types.push_back(return_type_id);
  481. for (auto id : region_input_ids) {
  482. return_and_parameter_types.push_back(
  483. ir_context->get_def_use_mgr()->GetDef(id)->type_id());
  484. }
  485. function_type_id =
  486. fuzzerutil::FindFunctionType(ir_context, return_and_parameter_types);
  487. }
  488. // If no existing function type was found, we need to create one.
  489. if (function_type_id == 0) {
  490. assert(
  491. ((return_type_id == 0) == !region_output_ids.empty()) &&
  492. "We should only have set the return type if there are no output ids.");
  493. // If the region generates output ids, we need to make a struct with one
  494. // field per output id.
  495. if (!region_output_ids.empty()) {
  496. opt::Instruction::OperandList struct_member_types;
  497. for (uint32_t output_id : region_output_ids) {
  498. auto output_id_type =
  499. ir_context->get_def_use_mgr()->GetDef(output_id)->type_id();
  500. struct_member_types.push_back({SPV_OPERAND_TYPE_ID, {output_id_type}});
  501. }
  502. // Add a new struct type to the module.
  503. ir_context->module()->AddType(MakeUnique<opt::Instruction>(
  504. ir_context, SpvOpTypeStruct, 0,
  505. message_.new_function_struct_return_type_id(),
  506. std::move(struct_member_types)));
  507. // The return type for the function is the newly-created struct.
  508. return_type_id = message_.new_function_struct_return_type_id();
  509. }
  510. assert(
  511. return_type_id != 0 &&
  512. "We should either have a void return type, or have created a struct.");
  513. // The region's input ids dictate the parameter types to the function.
  514. opt::Instruction::OperandList function_type_operands;
  515. function_type_operands.push_back({SPV_OPERAND_TYPE_ID, {return_type_id}});
  516. for (auto id : region_input_ids) {
  517. function_type_operands.push_back(
  518. {SPV_OPERAND_TYPE_ID,
  519. {ir_context->get_def_use_mgr()->GetDef(id)->type_id()}});
  520. }
  521. // Add a new function type to the module, and record that this is the type
  522. // id for the new function.
  523. ir_context->module()->AddType(MakeUnique<opt::Instruction>(
  524. ir_context, SpvOpTypeFunction, 0, message_.new_function_type_id(),
  525. function_type_operands));
  526. function_type_id = message_.new_function_type_id();
  527. }
  528. // Create a new function with |message_.new_function_id| as the function id,
  529. // and the return type and function type prepared above.
  530. std::unique_ptr<opt::Function> outlined_function =
  531. MakeUnique<opt::Function>(MakeUnique<opt::Instruction>(
  532. ir_context, SpvOpFunction, return_type_id, message_.new_function_id(),
  533. opt::Instruction::OperandList(
  534. {{spv_operand_type_t ::SPV_OPERAND_TYPE_LITERAL_INTEGER,
  535. {SpvFunctionControlMaskNone}},
  536. {spv_operand_type_t::SPV_OPERAND_TYPE_ID,
  537. {function_type_id}}})));
  538. // Add one parameter to the function for each input id, using the fresh ids
  539. // provided in |input_id_to_fresh_id_map|.
  540. for (auto id : region_input_ids) {
  541. outlined_function->AddParameter(MakeUnique<opt::Instruction>(
  542. ir_context, SpvOpFunctionParameter,
  543. ir_context->get_def_use_mgr()->GetDef(id)->type_id(),
  544. input_id_to_fresh_id_map.at(id), opt::Instruction::OperandList()));
  545. // If the input id is an irrelevant-valued variable, the same should be true
  546. // of the corresponding parameter.
  547. if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant(
  548. id)) {
  549. transformation_context->GetFactManager()
  550. ->AddFactValueOfPointeeIsIrrelevant(input_id_to_fresh_id_map.at(id));
  551. }
  552. }
  553. return outlined_function;
  554. }
  555. void TransformationOutlineFunction::UpdateModuleIdBoundForFreshIds(
  556. opt::IRContext* ir_context,
  557. const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
  558. const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
  559. // Enlarge the module's id bound as needed to accommodate the various fresh
  560. // ids associated with the transformation.
  561. fuzzerutil::UpdateModuleIdBound(
  562. ir_context, message_.new_function_struct_return_type_id());
  563. fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_type_id());
  564. fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_function_id());
  565. fuzzerutil::UpdateModuleIdBound(ir_context,
  566. message_.new_function_region_entry_block());
  567. fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_caller_result_id());
  568. fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_callee_result_id());
  569. for (auto& entry : input_id_to_fresh_id_map) {
  570. fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
  571. }
  572. for (auto& entry : output_id_to_fresh_id_map) {
  573. fuzzerutil::UpdateModuleIdBound(ir_context, entry.second);
  574. }
  575. }
  576. void TransformationOutlineFunction::RemapInputAndOutputIdsInRegion(
  577. opt::IRContext* ir_context,
  578. const opt::BasicBlock& original_region_exit_block,
  579. const std::set<opt::BasicBlock*>& region_blocks,
  580. const std::vector<uint32_t>& region_input_ids,
  581. const std::vector<uint32_t>& region_output_ids,
  582. const std::map<uint32_t, uint32_t>& input_id_to_fresh_id_map,
  583. const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map) const {
  584. // Change all uses of input ids inside the region to the corresponding fresh
  585. // ids that will ultimately be parameters of the outlined function.
  586. // This is done by considering each region input id in turn.
  587. for (uint32_t id : region_input_ids) {
  588. // We then consider each use of the input id.
  589. ir_context->get_def_use_mgr()->ForEachUse(
  590. id, [ir_context, id, &input_id_to_fresh_id_map, region_blocks](
  591. opt::Instruction* use, uint32_t operand_index) {
  592. // Find the block in which this use of the input id occurs.
  593. opt::BasicBlock* use_block = ir_context->get_instr_block(use);
  594. // We want to rewrite the use id if its block occurs in the outlined
  595. // region.
  596. if (region_blocks.count(use_block) != 0) {
  597. // Rewrite this use of the input id.
  598. use->SetOperand(operand_index, {input_id_to_fresh_id_map.at(id)});
  599. }
  600. });
  601. }
  602. // Change each definition of a region output id to define the corresponding
  603. // fresh ids that will store intermediate value for the output ids. Also
  604. // change all uses of the output id located in the outlined region.
  605. // This is done by considering each region output id in turn.
  606. for (uint32_t id : region_output_ids) {
  607. // First consider each use of the output id and update the relevant uses.
  608. ir_context->get_def_use_mgr()->ForEachUse(
  609. id, [ir_context, &original_region_exit_block, id,
  610. &output_id_to_fresh_id_map,
  611. region_blocks](opt::Instruction* use, uint32_t operand_index) {
  612. // Find the block in which this use of the output id occurs.
  613. auto use_block = ir_context->get_instr_block(use);
  614. // We want to rewrite the use id if its block occurs in the outlined
  615. // region, with one exception: the terminator of the exit block of
  616. // the region is going to remain in the original function, so if the
  617. // use appears in such a terminator instruction we leave it alone.
  618. if (
  619. // The block is in the region ...
  620. region_blocks.count(use_block) != 0 &&
  621. // ... and the use is not in the terminator instruction of the
  622. // region's exit block.
  623. !(use_block == &original_region_exit_block &&
  624. use->IsBlockTerminator())) {
  625. // Rewrite this use of the output id.
  626. use->SetOperand(operand_index, {output_id_to_fresh_id_map.at(id)});
  627. }
  628. });
  629. // Now change the instruction that defines the output id so that it instead
  630. // defines the corresponding fresh id. We do this after changing all the
  631. // uses so that the definition of the original id is still registered when
  632. // we analyse its uses.
  633. ir_context->get_def_use_mgr()->GetDef(id)->SetResultId(
  634. output_id_to_fresh_id_map.at(id));
  635. }
  636. }
  637. void TransformationOutlineFunction::PopulateOutlinedFunction(
  638. const opt::BasicBlock& original_region_entry_block,
  639. const opt::BasicBlock& original_region_exit_block,
  640. const std::set<opt::BasicBlock*>& region_blocks,
  641. const std::vector<uint32_t>& region_output_ids,
  642. const std::map<uint32_t, uint32_t>& output_id_to_fresh_id_map,
  643. opt::IRContext* ir_context, opt::Function* outlined_function,
  644. TransformationContext* transformation_context) const {
  645. // When we create the exit block for the outlined region, we use this pointer
  646. // to track of it so that we can manipulate it later.
  647. opt::BasicBlock* outlined_region_exit_block = nullptr;
  648. // The region entry block in the new function is identical to the entry block
  649. // of the region being outlined, except that it has
  650. // |message_.new_function_region_entry_block| as its id.
  651. std::unique_ptr<opt::BasicBlock> outlined_region_entry_block =
  652. MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
  653. ir_context, SpvOpLabel, 0, message_.new_function_region_entry_block(),
  654. opt::Instruction::OperandList()));
  655. outlined_region_entry_block->SetParent(outlined_function);
  656. // If the original region's entry block was dead, the outlined region's entry
  657. // block is also dead.
  658. if (transformation_context->GetFactManager()->BlockIsDead(
  659. original_region_entry_block.id())) {
  660. transformation_context->GetFactManager()->AddFactBlockIsDead(
  661. outlined_region_entry_block->id());
  662. }
  663. if (&original_region_entry_block == &original_region_exit_block) {
  664. outlined_region_exit_block = outlined_region_entry_block.get();
  665. }
  666. for (auto& inst : original_region_entry_block) {
  667. outlined_region_entry_block->AddInstruction(
  668. std::unique_ptr<opt::Instruction>(inst.Clone(ir_context)));
  669. }
  670. outlined_function->AddBasicBlock(std::move(outlined_region_entry_block));
  671. // We now go through the single-entry single-exit region defined by the entry
  672. // and exit blocks, adding clones of all blocks to the new function.
  673. // Consider every block in the enclosing function.
  674. auto enclosing_function = original_region_entry_block.GetParent();
  675. for (auto block_it = enclosing_function->begin();
  676. block_it != enclosing_function->end();) {
  677. // Skip the region's entry block - we already dealt with it above.
  678. if (region_blocks.count(&*block_it) == 0 ||
  679. &*block_it == &original_region_entry_block) {
  680. ++block_it;
  681. continue;
  682. }
  683. // Clone the block so that it can be added to the new function.
  684. auto cloned_block =
  685. std::unique_ptr<opt::BasicBlock>(block_it->Clone(ir_context));
  686. // If this is the region's exit block, then the cloned block is the outlined
  687. // region's exit block.
  688. if (&*block_it == &original_region_exit_block) {
  689. assert(outlined_region_exit_block == nullptr &&
  690. "We should not yet have encountered the exit block.");
  691. outlined_region_exit_block = cloned_block.get();
  692. }
  693. cloned_block->SetParent(outlined_function);
  694. // Redirect any OpPhi operands whose predecessors are the original region
  695. // entry block to become the new function entry block.
  696. cloned_block->ForEachPhiInst([this](opt::Instruction* phi_inst) {
  697. for (uint32_t predecessor_index = 1;
  698. predecessor_index < phi_inst->NumInOperands();
  699. predecessor_index += 2) {
  700. if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
  701. message_.entry_block()) {
  702. phi_inst->SetInOperand(predecessor_index,
  703. {message_.new_function_region_entry_block()});
  704. }
  705. }
  706. });
  707. outlined_function->AddBasicBlock(std::move(cloned_block));
  708. block_it = block_it.Erase();
  709. }
  710. assert(outlined_region_exit_block != nullptr &&
  711. "We should have encountered the region's exit block when iterating "
  712. "through the function");
  713. // We now need to adapt the exit block for the region - in the new function -
  714. // so that it ends with a return.
  715. // We first eliminate the merge instruction (if any) and the terminator for
  716. // the cloned exit block.
  717. for (auto inst_it = outlined_region_exit_block->begin();
  718. inst_it != outlined_region_exit_block->end();) {
  719. if (inst_it->opcode() == SpvOpLoopMerge ||
  720. inst_it->opcode() == SpvOpSelectionMerge) {
  721. inst_it = inst_it.Erase();
  722. } else if (inst_it->IsBlockTerminator()) {
  723. inst_it = inst_it.Erase();
  724. } else {
  725. ++inst_it;
  726. }
  727. }
  728. // We now add either OpReturn or OpReturnValue as the cloned exit block's
  729. // terminator.
  730. if (region_output_ids.empty()) {
  731. // The case where there are no region output ids is simple: we just add
  732. // OpReturn.
  733. outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
  734. ir_context, SpvOpReturn, 0, 0, opt::Instruction::OperandList()));
  735. } else {
  736. // In the case where there are output ids, we add an OpCompositeConstruct
  737. // instruction to pack all the output values into a struct, and then an
  738. // OpReturnValue instruction to return this struct.
  739. opt::Instruction::OperandList struct_member_operands;
  740. for (uint32_t id : region_output_ids) {
  741. struct_member_operands.push_back(
  742. {SPV_OPERAND_TYPE_ID, {output_id_to_fresh_id_map.at(id)}});
  743. }
  744. outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
  745. ir_context, SpvOpCompositeConstruct,
  746. message_.new_function_struct_return_type_id(),
  747. message_.new_callee_result_id(), struct_member_operands));
  748. outlined_region_exit_block->AddInstruction(MakeUnique<opt::Instruction>(
  749. ir_context, SpvOpReturnValue, 0, 0,
  750. opt::Instruction::OperandList(
  751. {{SPV_OPERAND_TYPE_ID, {message_.new_callee_result_id()}}})));
  752. }
  753. outlined_function->SetFunctionEnd(MakeUnique<opt::Instruction>(
  754. ir_context, SpvOpFunctionEnd, 0, 0, opt::Instruction::OperandList()));
  755. }
  756. void TransformationOutlineFunction::ShrinkOriginalRegion(
  757. opt::IRContext* ir_context, std::set<opt::BasicBlock*>& region_blocks,
  758. const std::vector<uint32_t>& region_input_ids,
  759. const std::vector<uint32_t>& region_output_ids,
  760. const std::map<uint32_t, uint32_t>& output_id_to_type_id,
  761. uint32_t return_type_id,
  762. std::unique_ptr<opt::Instruction> cloned_exit_block_merge,
  763. std::unique_ptr<opt::Instruction> cloned_exit_block_terminator,
  764. opt::BasicBlock* original_region_entry_block) const {
  765. // Erase all blocks from the original function that are in the outlined
  766. // region, except for the region's entry block.
  767. //
  768. // In the process, identify all references to the exit block of the region,
  769. // as merge blocks, continue targets, or OpPhi predecessors, and rewrite them
  770. // to refer to the region entry block (the single block to which we are
  771. // shrinking the region).
  772. auto enclosing_function = original_region_entry_block->GetParent();
  773. for (auto block_it = enclosing_function->begin();
  774. block_it != enclosing_function->end();) {
  775. if (&*block_it == original_region_entry_block) {
  776. ++block_it;
  777. } else if (region_blocks.count(&*block_it) == 0) {
  778. // The block is not in the region. Check whether it has the last block
  779. // of the region as an OpPhi predecessor, and if so change the
  780. // predecessor to be the first block of the region (i.e. the block
  781. // containing the call to what was outlined).
  782. assert(block_it->MergeBlockIdIfAny() != message_.exit_block() &&
  783. "Outlined region must not end with a merge block");
  784. assert(block_it->ContinueBlockIdIfAny() != message_.exit_block() &&
  785. "Outlined region must not end with a continue target");
  786. block_it->ForEachPhiInst([this](opt::Instruction* phi_inst) {
  787. for (uint32_t predecessor_index = 1;
  788. predecessor_index < phi_inst->NumInOperands();
  789. predecessor_index += 2) {
  790. if (phi_inst->GetSingleWordInOperand(predecessor_index) ==
  791. message_.exit_block()) {
  792. phi_inst->SetInOperand(predecessor_index, {message_.entry_block()});
  793. }
  794. }
  795. });
  796. ++block_it;
  797. } else {
  798. // The block is in the region and is not the region's entry block: kill
  799. // it.
  800. block_it = block_it.Erase();
  801. }
  802. }
  803. // Now erase all instructions from the region's entry block, as they have
  804. // been outlined.
  805. for (auto inst_it = original_region_entry_block->begin();
  806. inst_it != original_region_entry_block->end();) {
  807. inst_it = inst_it.Erase();
  808. }
  809. // Now we add a call to the outlined function to the region's entry block.
  810. opt::Instruction::OperandList function_call_operands;
  811. function_call_operands.push_back(
  812. {SPV_OPERAND_TYPE_ID, {message_.new_function_id()}});
  813. // The function parameters are the region input ids.
  814. for (auto input_id : region_input_ids) {
  815. function_call_operands.push_back({SPV_OPERAND_TYPE_ID, {input_id}});
  816. }
  817. original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
  818. ir_context, SpvOpFunctionCall, return_type_id,
  819. message_.new_caller_result_id(), function_call_operands));
  820. // If there are output ids, the function call will return a struct. For each
  821. // output id, we add an extract operation to pull the appropriate struct
  822. // member out into an output id.
  823. for (uint32_t index = 0; index < region_output_ids.size(); ++index) {
  824. uint32_t output_id = region_output_ids[index];
  825. original_region_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
  826. ir_context, SpvOpCompositeExtract, output_id_to_type_id.at(output_id),
  827. output_id,
  828. opt::Instruction::OperandList(
  829. {{SPV_OPERAND_TYPE_ID, {message_.new_caller_result_id()}},
  830. {SPV_OPERAND_TYPE_LITERAL_INTEGER, {index}}})));
  831. }
  832. // Finally, we terminate the block with the merge instruction (if any) that
  833. // used to belong to the region's exit block, and the terminator that used
  834. // to belong to the region's exit block.
  835. if (cloned_exit_block_merge != nullptr) {
  836. original_region_entry_block->AddInstruction(
  837. std::move(cloned_exit_block_merge));
  838. }
  839. original_region_entry_block->AddInstruction(
  840. std::move(cloned_exit_block_terminator));
  841. }
  842. } // namespace fuzz
  843. } // namespace spvtools