transformation_outline_function.cpp 40 KB

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