transformation_outline_function.cpp 40 KB

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