transformation_outline_function.cpp 43 KB

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