transformation_outline_function.cpp 39 KB

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