transformation_flatten_conditional_branch.cpp 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. // Copyright (c) 2020 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_flatten_conditional_branch.h"
  15. #include "source/fuzz/fuzzer_util.h"
  16. #include "source/fuzz/instruction_descriptor.h"
  17. namespace spvtools {
  18. namespace fuzz {
  19. TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
  20. protobufs::TransformationFlattenConditionalBranch message)
  21. : message_(std::move(message)) {}
  22. TransformationFlattenConditionalBranch::TransformationFlattenConditionalBranch(
  23. uint32_t header_block_id, bool true_branch_first,
  24. uint32_t fresh_id_for_bvec2_selector, uint32_t fresh_id_for_bvec3_selector,
  25. uint32_t fresh_id_for_bvec4_selector,
  26. const std::vector<protobufs::SideEffectWrapperInfo>&
  27. side_effect_wrappers_info) {
  28. message_.set_header_block_id(header_block_id);
  29. message_.set_true_branch_first(true_branch_first);
  30. message_.set_fresh_id_for_bvec2_selector(fresh_id_for_bvec2_selector);
  31. message_.set_fresh_id_for_bvec3_selector(fresh_id_for_bvec3_selector);
  32. message_.set_fresh_id_for_bvec4_selector(fresh_id_for_bvec4_selector);
  33. for (auto const& side_effect_wrapper_info : side_effect_wrappers_info) {
  34. *message_.add_side_effect_wrapper_info() = side_effect_wrapper_info;
  35. }
  36. }
  37. bool TransformationFlattenConditionalBranch::IsApplicable(
  38. opt::IRContext* ir_context,
  39. const TransformationContext& transformation_context) const {
  40. auto header_block =
  41. fuzzerutil::MaybeFindBlock(ir_context, message_.header_block_id());
  42. // The block must have been found and it must be a selection header.
  43. if (!header_block || !header_block->GetMergeInst() ||
  44. header_block->GetMergeInst()->opcode() != spv::Op::OpSelectionMerge) {
  45. return false;
  46. }
  47. // The header block must end with an OpBranchConditional instruction.
  48. if (header_block->terminator()->opcode() != spv::Op::OpBranchConditional) {
  49. return false;
  50. }
  51. // The branch condition cannot be irrelevant: we will make reference to it
  52. // multiple times and we need to be guaranteed that these references will
  53. // yield the same result; if they are replaced by other ids that will not
  54. // work.
  55. if (transformation_context.GetFactManager()->IdIsIrrelevant(
  56. header_block->terminator()->GetSingleWordInOperand(0))) {
  57. return false;
  58. }
  59. std::set<uint32_t> used_fresh_ids;
  60. // If ids have been provided to be used as vector guards for OpSelect
  61. // instructions then they must be fresh.
  62. for (uint32_t fresh_id_for_bvec_selector :
  63. {message_.fresh_id_for_bvec2_selector(),
  64. message_.fresh_id_for_bvec3_selector(),
  65. message_.fresh_id_for_bvec4_selector()}) {
  66. if (fresh_id_for_bvec_selector != 0) {
  67. if (!CheckIdIsFreshAndNotUsedByThisTransformation(
  68. fresh_id_for_bvec_selector, ir_context, &used_fresh_ids)) {
  69. return false;
  70. }
  71. }
  72. }
  73. // Use a set to keep track of the instructions that require fresh ids.
  74. std::set<opt::Instruction*> instructions_that_need_ids;
  75. // Check that, if there are enough ids, the conditional can be flattened and,
  76. // if so, add all the problematic instructions that need to be enclosed inside
  77. // conditionals to |instructions_that_need_ids|.
  78. if (!GetProblematicInstructionsIfConditionalCanBeFlattened(
  79. ir_context, header_block, transformation_context,
  80. &instructions_that_need_ids)) {
  81. return false;
  82. }
  83. // Get the mapping from instructions to the fresh ids needed to enclose them
  84. // inside conditionals.
  85. auto insts_to_wrapper_info = GetInstructionsToWrapperInfo(ir_context);
  86. {
  87. // Check the ids in the map.
  88. for (const auto& inst_to_info : insts_to_wrapper_info) {
  89. // Check the fresh ids needed for all of the instructions that need to be
  90. // enclosed inside a conditional.
  91. for (uint32_t id : {inst_to_info.second.merge_block_id(),
  92. inst_to_info.second.execute_block_id()}) {
  93. if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
  94. id, ir_context, &used_fresh_ids)) {
  95. return false;
  96. }
  97. }
  98. // Check the other ids needed, if the instruction needs a placeholder.
  99. if (InstructionNeedsPlaceholder(ir_context, *inst_to_info.first)) {
  100. // Check the fresh ids.
  101. for (uint32_t id : {inst_to_info.second.actual_result_id(),
  102. inst_to_info.second.alternative_block_id(),
  103. inst_to_info.second.placeholder_result_id()}) {
  104. if (!id || !CheckIdIsFreshAndNotUsedByThisTransformation(
  105. id, ir_context, &used_fresh_ids)) {
  106. return false;
  107. }
  108. }
  109. // Check that the placeholder value id exists, has the right type and is
  110. // available to use at this point.
  111. auto value_def = ir_context->get_def_use_mgr()->GetDef(
  112. inst_to_info.second.value_to_copy_id());
  113. if (!value_def ||
  114. value_def->type_id() != inst_to_info.first->type_id() ||
  115. !fuzzerutil::IdIsAvailableBeforeInstruction(
  116. ir_context, inst_to_info.first,
  117. inst_to_info.second.value_to_copy_id())) {
  118. return false;
  119. }
  120. }
  121. }
  122. }
  123. // If some instructions that require ids are not in the map, the
  124. // transformation needs overflow ids to be applicable.
  125. for (auto instruction : instructions_that_need_ids) {
  126. if (insts_to_wrapper_info.count(instruction) == 0 &&
  127. !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
  128. return false;
  129. }
  130. }
  131. if (OpSelectArgumentsAreRestricted(ir_context)) {
  132. // OpPhi instructions at the convergence block for the selection are handled
  133. // by turning them into OpSelect instructions. As the SPIR-V version in use
  134. // has restrictions on the arguments that OpSelect can take, we must check
  135. // that any OpPhi instructions are compatible with these restrictions.
  136. uint32_t convergence_block_id =
  137. FindConvergenceBlock(ir_context, *header_block);
  138. // Consider every OpPhi instruction at the convergence block.
  139. if (!ir_context->cfg()
  140. ->block(convergence_block_id)
  141. ->WhileEachPhiInst([this,
  142. ir_context](opt::Instruction* inst) -> bool {
  143. // Decide whether the OpPhi can be handled based on its result
  144. // type.
  145. opt::Instruction* phi_result_type =
  146. ir_context->get_def_use_mgr()->GetDef(inst->type_id());
  147. switch (phi_result_type->opcode()) {
  148. case spv::Op::OpTypeBool:
  149. case spv::Op::OpTypeInt:
  150. case spv::Op::OpTypeFloat:
  151. case spv::Op::OpTypePointer:
  152. // Fine: OpSelect can work directly on scalar and pointer
  153. // types.
  154. return true;
  155. case spv::Op::OpTypeVector: {
  156. // In its restricted form, OpSelect can only select between
  157. // vectors if the condition of the select is a boolean
  158. // boolean vector. We thus require the appropriate boolean
  159. // vector type to be present.
  160. uint32_t bool_type_id =
  161. fuzzerutil::MaybeGetBoolType(ir_context);
  162. if (!bool_type_id) {
  163. return false;
  164. }
  165. uint32_t dimension =
  166. phi_result_type->GetSingleWordInOperand(1);
  167. if (fuzzerutil::MaybeGetVectorType(ir_context, bool_type_id,
  168. dimension) == 0) {
  169. // The required boolean vector type is not present.
  170. return false;
  171. }
  172. // The transformation needs to be equipped with a fresh id
  173. // in which to store the vectorized version of the selection
  174. // construct's condition.
  175. switch (dimension) {
  176. case 2:
  177. return message_.fresh_id_for_bvec2_selector() != 0;
  178. case 3:
  179. return message_.fresh_id_for_bvec3_selector() != 0;
  180. default:
  181. assert(dimension == 4 && "Invalid vector dimension.");
  182. return message_.fresh_id_for_bvec4_selector() != 0;
  183. }
  184. }
  185. default:
  186. return false;
  187. }
  188. })) {
  189. return false;
  190. }
  191. }
  192. // All checks were passed.
  193. return true;
  194. }
  195. void TransformationFlattenConditionalBranch::Apply(
  196. opt::IRContext* ir_context,
  197. TransformationContext* transformation_context) const {
  198. // branch = 1 corresponds to the true branch, branch = 2 corresponds to the
  199. // false branch. If the true branch is to be laid out first, we need to visit
  200. // the false branch first, because each branch is moved to right after the
  201. // header while it is visited.
  202. std::vector<uint32_t> branches = {2, 1};
  203. if (!message_.true_branch_first()) {
  204. // Similarly, we need to visit the true branch first, if we want it to be
  205. // laid out after the false branch.
  206. branches = {1, 2};
  207. }
  208. auto header_block = ir_context->cfg()->block(message_.header_block_id());
  209. // Get the ids of the starting blocks of the first and last branches to be
  210. // laid out. The first branch is the true branch iff
  211. // |message_.true_branch_first| is true.
  212. auto branch_instruction = header_block->terminator();
  213. uint32_t first_block_first_branch_id =
  214. branch_instruction->GetSingleWordInOperand(branches[1]);
  215. uint32_t first_block_last_branch_id =
  216. branch_instruction->GetSingleWordInOperand(branches[0]);
  217. uint32_t convergence_block_id =
  218. FindConvergenceBlock(ir_context, *header_block);
  219. // If the OpBranchConditional instruction in the header branches to the same
  220. // block for both values of the condition, this is the convergence block (the
  221. // flow does not actually diverge) and the OpPhi instructions in it are still
  222. // valid, so we do not need to make any changes.
  223. if (first_block_first_branch_id != first_block_last_branch_id) {
  224. RewriteOpPhiInstructionsAtConvergenceBlock(
  225. *header_block, convergence_block_id, ir_context);
  226. }
  227. // Get the mapping from instructions to fresh ids.
  228. auto insts_to_info = GetInstructionsToWrapperInfo(ir_context);
  229. // Get a reference to the last block in the first branch that will be laid out
  230. // (this depends on |message_.true_branch_first|). The last block is the block
  231. // in the branch just before flow converges (it might not exist).
  232. opt::BasicBlock* last_block_first_branch = nullptr;
  233. // Keep track of blocks and ids for which we should later add dead block and
  234. // irrelevant id facts. We wait until we have finished applying the
  235. // transformation before adding these facts, so that the fact manager has
  236. // access to the fully up-to-date module.
  237. std::vector<uint32_t> dead_blocks;
  238. std::vector<uint32_t> irrelevant_ids;
  239. // Adjust the conditional branches by enclosing problematic instructions
  240. // within conditionals and get references to the last block in each branch.
  241. for (uint32_t branch : branches) {
  242. auto current_block = header_block;
  243. // Get the id of the first block in this branch.
  244. uint32_t next_block_id = branch_instruction->GetSingleWordInOperand(branch);
  245. // Consider all blocks in the branch until the convergence block is reached.
  246. while (next_block_id != convergence_block_id) {
  247. // Move the next block to right after the current one.
  248. current_block->GetParent()->MoveBasicBlockToAfter(next_block_id,
  249. current_block);
  250. // Move forward in the branch.
  251. current_block = ir_context->cfg()->block(next_block_id);
  252. // Find all the instructions in the current block which need to be
  253. // enclosed inside conditionals.
  254. std::vector<opt::Instruction*> problematic_instructions;
  255. current_block->ForEachInst(
  256. [&problematic_instructions](opt::Instruction* instruction) {
  257. if (instruction->opcode() != spv::Op::OpLabel &&
  258. instruction->opcode() != spv::Op::OpBranch &&
  259. !fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
  260. problematic_instructions.push_back(instruction);
  261. }
  262. });
  263. uint32_t condition_id =
  264. header_block->terminator()->GetSingleWordInOperand(0);
  265. // Enclose all of the problematic instructions in conditionals, with the
  266. // same condition as the selection construct being flattened.
  267. for (auto instruction : problematic_instructions) {
  268. // Get the info needed by this instruction to wrap it inside a
  269. // conditional.
  270. protobufs::SideEffectWrapperInfo wrapper_info;
  271. if (insts_to_info.count(instruction) != 0) {
  272. // Get the fresh ids from the map, if present.
  273. wrapper_info = insts_to_info[instruction];
  274. } else {
  275. // If we could not get it from the map, use overflow ids. We don't
  276. // need to set |wrapper_info.instruction|, as it will not be used.
  277. wrapper_info.set_merge_block_id(
  278. transformation_context->GetOverflowIdSource()
  279. ->GetNextOverflowId());
  280. wrapper_info.set_execute_block_id(
  281. transformation_context->GetOverflowIdSource()
  282. ->GetNextOverflowId());
  283. if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
  284. // Ge the fresh ids from the overflow ids.
  285. wrapper_info.set_actual_result_id(
  286. transformation_context->GetOverflowIdSource()
  287. ->GetNextOverflowId());
  288. wrapper_info.set_alternative_block_id(
  289. transformation_context->GetOverflowIdSource()
  290. ->GetNextOverflowId());
  291. wrapper_info.set_placeholder_result_id(
  292. transformation_context->GetOverflowIdSource()
  293. ->GetNextOverflowId());
  294. // Try to find a zero constant. It does not matter whether it is
  295. // relevant or irrelevant.
  296. for (bool is_irrelevant : {true, false}) {
  297. wrapper_info.set_value_to_copy_id(
  298. fuzzerutil::MaybeGetZeroConstant(
  299. ir_context, *transformation_context,
  300. instruction->type_id(), is_irrelevant));
  301. if (wrapper_info.value_to_copy_id()) {
  302. break;
  303. }
  304. }
  305. }
  306. }
  307. // Enclose the instruction in a conditional and get the merge block
  308. // generated by this operation (this is where all the following
  309. // instructions will be).
  310. current_block = EncloseInstructionInConditional(
  311. ir_context, *transformation_context, current_block, instruction,
  312. wrapper_info, condition_id, branch == 1, &dead_blocks,
  313. &irrelevant_ids);
  314. }
  315. next_block_id = current_block->terminator()->GetSingleWordInOperand(0);
  316. // If the next block is the convergence block and this the branch that
  317. // will be laid out right after the header, record this as the last block
  318. // in the first branch.
  319. if (next_block_id == convergence_block_id && branch == branches[1]) {
  320. last_block_first_branch = current_block;
  321. }
  322. }
  323. }
  324. // The current header should unconditionally branch to the starting block in
  325. // the first branch to be laid out, if such a branch exists (i.e. the header
  326. // does not branch directly to the convergence block), and to the starting
  327. // block in the last branch to be laid out otherwise.
  328. uint32_t after_header = first_block_first_branch_id != convergence_block_id
  329. ? first_block_first_branch_id
  330. : first_block_last_branch_id;
  331. // Kill the merge instruction and the branch instruction in the current
  332. // header.
  333. auto merge_inst = header_block->GetMergeInst();
  334. ir_context->KillInst(branch_instruction);
  335. ir_context->KillInst(merge_inst);
  336. // Add a new, unconditional, branch instruction from the current header to
  337. // |after_header|.
  338. header_block->AddInstruction(MakeUnique<opt::Instruction>(
  339. ir_context, spv::Op::OpBranch, 0, 0,
  340. opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {after_header}}}));
  341. // If the first branch to be laid out exists, change the branch instruction so
  342. // that the last block in such branch unconditionally branches to the first
  343. // block in the other branch (or the convergence block if there is no other
  344. // branch) and change the OpPhi instructions in the last branch accordingly
  345. // (the predecessor changed).
  346. if (last_block_first_branch) {
  347. last_block_first_branch->terminator()->SetInOperand(
  348. 0, {first_block_last_branch_id});
  349. // Change the OpPhi instructions of the last branch (if there is another
  350. // branch) so that the predecessor is now the last block of the first
  351. // branch. The block must have a single predecessor, so the operand
  352. // specifying the predecessor is always in the same position.
  353. if (first_block_last_branch_id != convergence_block_id) {
  354. ir_context->get_instr_block(first_block_last_branch_id)
  355. ->ForEachPhiInst(
  356. [&last_block_first_branch](opt::Instruction* phi_inst) {
  357. // The operand specifying the predecessor is the second input
  358. // operand.
  359. phi_inst->SetInOperand(1, {last_block_first_branch->id()});
  360. });
  361. }
  362. }
  363. // Invalidate all analyses
  364. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
  365. // Now that we have finished adding blocks and ids to the module and
  366. // invalidated existing analyses, update the fact manager regarding dead
  367. // blocks and irrelevant ids.
  368. for (auto dead_block : dead_blocks) {
  369. transformation_context->GetFactManager()->AddFactBlockIsDead(dead_block);
  370. }
  371. for (auto irrelevant_id : irrelevant_ids) {
  372. transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
  373. irrelevant_id);
  374. }
  375. }
  376. protobufs::Transformation TransformationFlattenConditionalBranch::ToMessage()
  377. const {
  378. protobufs::Transformation result;
  379. *result.mutable_flatten_conditional_branch() = message_;
  380. return result;
  381. }
  382. bool TransformationFlattenConditionalBranch::
  383. GetProblematicInstructionsIfConditionalCanBeFlattened(
  384. opt::IRContext* ir_context, opt::BasicBlock* header,
  385. const TransformationContext& transformation_context,
  386. std::set<opt::Instruction*>* instructions_that_need_ids) {
  387. uint32_t merge_block_id = header->MergeBlockIdIfAny();
  388. assert(merge_block_id &&
  389. header->GetMergeInst()->opcode() == spv::Op::OpSelectionMerge &&
  390. header->terminator()->opcode() == spv::Op::OpBranchConditional &&
  391. "|header| must be the header of a conditional.");
  392. // |header| must be reachable.
  393. if (!ir_context->IsReachable(*header)) {
  394. return false;
  395. }
  396. auto enclosing_function = header->GetParent();
  397. auto dominator_analysis =
  398. ir_context->GetDominatorAnalysis(enclosing_function);
  399. auto postdominator_analysis =
  400. ir_context->GetPostDominatorAnalysis(enclosing_function);
  401. // Check that the header and the merge block describe a single-entry,
  402. // single-exit region.
  403. if (!dominator_analysis->Dominates(header->id(), merge_block_id) ||
  404. !postdominator_analysis->Dominates(merge_block_id, header->id())) {
  405. return false;
  406. }
  407. // Traverse the CFG starting from the header and check that, for all the
  408. // blocks that can be reached by the header before the flow converges:
  409. // - they don't contain merge, barrier or OpSampledImage instructions
  410. // - they branch unconditionally to another block
  411. // Add any side-effecting instruction, requiring fresh ids, to
  412. // |instructions_that_need_ids|
  413. std::queue<uint32_t> to_check;
  414. header->ForEachSuccessorLabel(
  415. [&to_check](uint32_t label) { to_check.push(label); });
  416. auto* structured_cfg = ir_context->GetStructuredCFGAnalysis();
  417. while (!to_check.empty()) {
  418. uint32_t block_id = to_check.front();
  419. to_check.pop();
  420. if (structured_cfg->ContainingConstruct(block_id) != header->id() &&
  421. block_id != merge_block_id) {
  422. // This block can be reached from the |header| but doesn't belong to its
  423. // selection construct. This might be a continue target of some loop -
  424. // we can't flatten the |header|.
  425. return false;
  426. }
  427. // If the block post-dominates the header, this is where flow converges, and
  428. // we don't need to check this branch any further, because the
  429. // transformation will only change the part of the graph where flow is
  430. // divergent.
  431. if (postdominator_analysis->Dominates(block_id, header->id())) {
  432. continue;
  433. }
  434. if (!transformation_context.GetFactManager()->BlockIsDead(header->id()) &&
  435. transformation_context.GetFactManager()->BlockIsDead(block_id)) {
  436. // The |header| is not dead but the |block_id| is. Since |block_id|
  437. // doesn't postdominate the |header|, CFG hasn't converged yet. Thus, we
  438. // don't flatten the construct to prevent |block_id| from becoming
  439. // executable.
  440. return false;
  441. }
  442. auto block = ir_context->cfg()->block(block_id);
  443. // The block must not have a merge instruction, because inner constructs are
  444. // not allowed.
  445. if (block->GetMergeInst()) {
  446. return false;
  447. }
  448. // The terminator instruction for the block must be OpBranch.
  449. if (block->terminator()->opcode() != spv::Op::OpBranch) {
  450. return false;
  451. }
  452. // The base objects for all data descriptors involved in synonym facts.
  453. std::unordered_set<uint32_t> synonym_base_objects;
  454. for (auto* synonym :
  455. transformation_context.GetFactManager()->GetAllSynonyms()) {
  456. synonym_base_objects.insert(synonym->object());
  457. }
  458. // Check all of the instructions in the block.
  459. bool all_instructions_compatible = block->WhileEachInst(
  460. [ir_context, instructions_that_need_ids,
  461. &synonym_base_objects](opt::Instruction* instruction) {
  462. // We can ignore OpLabel instructions.
  463. if (instruction->opcode() == spv::Op::OpLabel) {
  464. return true;
  465. }
  466. // If the instruction is the base object of some synonym then we
  467. // conservatively bail out: if a synonym ends up depending on an
  468. // instruction that needs to be enclosed in a side-effect wrapper then
  469. // it might no longer hold after we flatten the conditional.
  470. if (instruction->result_id() &&
  471. synonym_base_objects.count(instruction->result_id())) {
  472. return false;
  473. }
  474. // If the instruction is a branch, it must be an unconditional branch.
  475. if (instruction->IsBranch()) {
  476. return instruction->opcode() == spv::Op::OpBranch;
  477. }
  478. // We cannot go ahead if we encounter an instruction that cannot be
  479. // handled.
  480. if (!InstructionCanBeHandled(ir_context, *instruction)) {
  481. return false;
  482. }
  483. // If the instruction has side effects, add it to the
  484. // |instructions_that_need_ids| set.
  485. if (!fuzzerutil::InstructionHasNoSideEffects(*instruction)) {
  486. instructions_that_need_ids->emplace(instruction);
  487. }
  488. return true;
  489. });
  490. if (!all_instructions_compatible) {
  491. return false;
  492. }
  493. // Add the successor of this block to the list of blocks that need to be
  494. // checked.
  495. to_check.push(block->terminator()->GetSingleWordInOperand(0));
  496. }
  497. // All the blocks are compatible with the transformation and this is indeed a
  498. // single-entry, single-exit region.
  499. return true;
  500. }
  501. bool TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
  502. opt::IRContext* ir_context, const opt::Instruction& instruction) {
  503. assert(!fuzzerutil::InstructionHasNoSideEffects(instruction) &&
  504. InstructionCanBeHandled(ir_context, instruction) &&
  505. "The instruction must have side effects and it must be possible to "
  506. "enclose it inside a conditional.");
  507. if (instruction.HasResultId()) {
  508. // We need a placeholder iff the type is not Void.
  509. auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
  510. return type && !type->AsVoid();
  511. }
  512. return false;
  513. }
  514. std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
  515. TransformationFlattenConditionalBranch::GetInstructionsToWrapperInfo(
  516. opt::IRContext* ir_context) const {
  517. std::unordered_map<opt::Instruction*, protobufs::SideEffectWrapperInfo>
  518. instructions_to_ids;
  519. for (const auto& wrapper_info : message_.side_effect_wrapper_info()) {
  520. auto instruction = FindInstruction(wrapper_info.instruction(), ir_context);
  521. if (instruction) {
  522. instructions_to_ids.emplace(instruction, wrapper_info);
  523. }
  524. }
  525. return instructions_to_ids;
  526. }
  527. opt::BasicBlock*
  528. TransformationFlattenConditionalBranch::EncloseInstructionInConditional(
  529. opt::IRContext* ir_context,
  530. const TransformationContext& transformation_context, opt::BasicBlock* block,
  531. opt::Instruction* instruction,
  532. const protobufs::SideEffectWrapperInfo& wrapper_info, uint32_t condition_id,
  533. bool exec_if_cond_true, std::vector<uint32_t>* dead_blocks,
  534. std::vector<uint32_t>* irrelevant_ids) {
  535. // Get the next instruction (it will be useful for splitting).
  536. auto next_instruction = instruction->NextNode();
  537. // Update the module id bound.
  538. for (uint32_t id :
  539. {wrapper_info.merge_block_id(), wrapper_info.execute_block_id()}) {
  540. fuzzerutil::UpdateModuleIdBound(ir_context, id);
  541. }
  542. // Create the block where the instruction is executed by splitting the
  543. // original block.
  544. auto execute_block = block->SplitBasicBlock(
  545. ir_context, wrapper_info.execute_block_id(),
  546. fuzzerutil::GetIteratorForInstruction(block, instruction));
  547. // Create the merge block for the conditional that we are about to create by
  548. // splitting execute_block (this will leave |instruction| as the only
  549. // instruction in |execute_block|).
  550. auto merge_block = execute_block->SplitBasicBlock(
  551. ir_context, wrapper_info.merge_block_id(),
  552. fuzzerutil::GetIteratorForInstruction(execute_block, next_instruction));
  553. // Propagate the fact that the block is dead to the newly-created blocks.
  554. if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
  555. dead_blocks->emplace_back(execute_block->id());
  556. dead_blocks->emplace_back(merge_block->id());
  557. }
  558. // Initially, consider the merge block as the alternative block to branch to
  559. // if the instruction should not be executed.
  560. auto alternative_block = merge_block;
  561. // Add an unconditional branch from |execute_block| to |merge_block|.
  562. execute_block->AddInstruction(MakeUnique<opt::Instruction>(
  563. ir_context, spv::Op::OpBranch, 0, 0,
  564. opt::Instruction::OperandList{
  565. {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
  566. // If the instruction requires a placeholder, it means that it has a result id
  567. // and its result needs to be able to be used later on, and we need to:
  568. // - add an additional block |ids.alternative_block_id| where a placeholder
  569. // result id (using fresh id |ids.placeholder_result_id|) is obtained either
  570. // by using OpCopyObject and copying |ids.value_to_copy_id| or, if such id
  571. // was not given and a suitable constant was not found, by using OpUndef.
  572. // - mark |ids.placeholder_result_id| as irrelevant
  573. // - change the result id of the instruction to a fresh id
  574. // (|ids.actual_result_id|).
  575. // - add an OpPhi instruction, which will have the original result id of the
  576. // instruction, in the merge block.
  577. if (InstructionNeedsPlaceholder(ir_context, *instruction)) {
  578. // Update the module id bound with the additional ids.
  579. for (uint32_t id :
  580. {wrapper_info.actual_result_id(), wrapper_info.alternative_block_id(),
  581. wrapper_info.placeholder_result_id()}) {
  582. fuzzerutil::UpdateModuleIdBound(ir_context, id);
  583. }
  584. // Create a new block using |fresh_ids.alternative_block_id| for its label.
  585. auto alternative_block_temp = MakeUnique<opt::BasicBlock>(
  586. MakeUnique<opt::Instruction>(ir_context, spv::Op::OpLabel, 0,
  587. wrapper_info.alternative_block_id(),
  588. opt::Instruction::OperandList{}));
  589. // Keep the original result id of the instruction in a variable.
  590. uint32_t original_result_id = instruction->result_id();
  591. // Set the result id of the instruction to be |ids.actual_result_id|.
  592. instruction->SetResultId(wrapper_info.actual_result_id());
  593. // Add a placeholder instruction, with the same type as the original
  594. // instruction and id |ids.placeholder_result_id|, to the new block.
  595. if (wrapper_info.value_to_copy_id()) {
  596. // If there is an available id to copy from, the placeholder instruction
  597. // will be %placeholder_result_id = OpCopyObject %type %value_to_copy_id
  598. alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
  599. ir_context, spv::Op::OpCopyObject, instruction->type_id(),
  600. wrapper_info.placeholder_result_id(),
  601. opt::Instruction::OperandList{
  602. {SPV_OPERAND_TYPE_ID, {wrapper_info.value_to_copy_id()}}}));
  603. } else {
  604. // If there is no such id, use an OpUndef instruction.
  605. alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
  606. ir_context, spv::Op::OpUndef, instruction->type_id(),
  607. wrapper_info.placeholder_result_id(),
  608. opt::Instruction::OperandList{}));
  609. }
  610. // Mark |ids.placeholder_result_id| as irrelevant.
  611. irrelevant_ids->emplace_back(wrapper_info.placeholder_result_id());
  612. // Add an unconditional branch from the new block to the merge block.
  613. alternative_block_temp->AddInstruction(MakeUnique<opt::Instruction>(
  614. ir_context, spv::Op::OpBranch, 0, 0,
  615. opt::Instruction::OperandList{
  616. {SPV_OPERAND_TYPE_ID, {merge_block->id()}}}));
  617. // Insert the block before the merge block.
  618. alternative_block = block->GetParent()->InsertBasicBlockBefore(
  619. std::move(alternative_block_temp), merge_block);
  620. // Using the original instruction result id, add an OpPhi instruction to the
  621. // merge block, which will either take the value of the result of the
  622. // instruction or the placeholder value defined in the alternative block.
  623. merge_block->begin().InsertBefore(MakeUnique<opt::Instruction>(
  624. ir_context, spv::Op::OpPhi, instruction->type_id(), original_result_id,
  625. opt::Instruction::OperandList{
  626. {SPV_OPERAND_TYPE_ID, {instruction->result_id()}},
  627. {SPV_OPERAND_TYPE_ID, {execute_block->id()}},
  628. {SPV_OPERAND_TYPE_ID, {wrapper_info.placeholder_result_id()}},
  629. {SPV_OPERAND_TYPE_ID, {alternative_block->id()}}}));
  630. // Propagate the fact that the block is dead to the new block.
  631. if (transformation_context.GetFactManager()->BlockIsDead(block->id())) {
  632. dead_blocks->emplace_back(alternative_block->id());
  633. }
  634. }
  635. // Depending on whether the instruction should be executed in the if branch or
  636. // in the else branch, get the corresponding ids.
  637. auto if_block_id = (exec_if_cond_true ? execute_block : alternative_block)
  638. ->GetLabel()
  639. ->result_id();
  640. auto else_block_id = (exec_if_cond_true ? alternative_block : execute_block)
  641. ->GetLabel()
  642. ->result_id();
  643. // Add an OpSelectionMerge instruction to the block.
  644. block->AddInstruction(MakeUnique<opt::Instruction>(
  645. ir_context, spv::Op::OpSelectionMerge, 0, 0,
  646. opt::Instruction::OperandList{
  647. {SPV_OPERAND_TYPE_ID, {merge_block->id()}},
  648. {SPV_OPERAND_TYPE_SELECTION_CONTROL,
  649. {uint32_t(spv::SelectionControlMask::MaskNone)}}}));
  650. // Add an OpBranchConditional, to the block, using |condition_id| as the
  651. // condition and branching to |if_block_id| if the condition is true and to
  652. // |else_block_id| if the condition is false.
  653. block->AddInstruction(MakeUnique<opt::Instruction>(
  654. ir_context, spv::Op::OpBranchConditional, 0, 0,
  655. opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {condition_id}},
  656. {SPV_OPERAND_TYPE_ID, {if_block_id}},
  657. {SPV_OPERAND_TYPE_ID, {else_block_id}}}));
  658. return merge_block;
  659. }
  660. bool TransformationFlattenConditionalBranch::InstructionCanBeHandled(
  661. opt::IRContext* ir_context, const opt::Instruction& instruction) {
  662. // We can handle all instructions with no side effects.
  663. if (fuzzerutil::InstructionHasNoSideEffects(instruction)) {
  664. return true;
  665. }
  666. // We cannot handle barrier instructions, while we should be able to handle
  667. // all other instructions by enclosing them inside a conditional.
  668. if (instruction.opcode() == spv::Op::OpControlBarrier ||
  669. instruction.opcode() == spv::Op::OpMemoryBarrier ||
  670. instruction.opcode() == spv::Op::OpNamedBarrierInitialize ||
  671. instruction.opcode() == spv::Op::OpMemoryNamedBarrier ||
  672. instruction.opcode() == spv::Op::OpTypeNamedBarrier) {
  673. return false;
  674. }
  675. // We cannot handle OpSampledImage instructions, as they need to be in the
  676. // same block as their use.
  677. if (instruction.opcode() == spv::Op::OpSampledImage) {
  678. return false;
  679. }
  680. // We cannot handle a sampled image load, because we re-work loads using
  681. // conditional branches and OpPhi instructions, and the result type of OpPhi
  682. // cannot be OpTypeSampledImage.
  683. if (instruction.opcode() == spv::Op::OpLoad &&
  684. ir_context->get_def_use_mgr()->GetDef(instruction.type_id())->opcode() ==
  685. spv::Op::OpTypeSampledImage) {
  686. return false;
  687. }
  688. // We cannot handle instructions with an id which return a void type, if the
  689. // result id is used in the module (e.g. a function call to a function that
  690. // returns nothing).
  691. if (instruction.HasResultId()) {
  692. auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
  693. assert(type && "The type should be found in the module");
  694. if (type->AsVoid() &&
  695. !ir_context->get_def_use_mgr()->WhileEachUse(
  696. instruction.result_id(),
  697. [](opt::Instruction* use_inst, uint32_t use_index) {
  698. // Return false if the id is used as an input operand.
  699. return use_index <
  700. use_inst->NumOperands() - use_inst->NumInOperands();
  701. })) {
  702. return false;
  703. }
  704. }
  705. return true;
  706. }
  707. std::unordered_set<uint32_t>
  708. TransformationFlattenConditionalBranch::GetFreshIds() const {
  709. std::unordered_set<uint32_t> result = {
  710. message_.fresh_id_for_bvec2_selector(),
  711. message_.fresh_id_for_bvec3_selector(),
  712. message_.fresh_id_for_bvec4_selector()};
  713. for (auto& side_effect_wrapper_info : message_.side_effect_wrapper_info()) {
  714. result.insert(side_effect_wrapper_info.merge_block_id());
  715. result.insert(side_effect_wrapper_info.execute_block_id());
  716. result.insert(side_effect_wrapper_info.actual_result_id());
  717. result.insert(side_effect_wrapper_info.alternative_block_id());
  718. result.insert(side_effect_wrapper_info.placeholder_result_id());
  719. }
  720. return result;
  721. }
  722. uint32_t TransformationFlattenConditionalBranch::FindConvergenceBlock(
  723. opt::IRContext* ir_context, const opt::BasicBlock& header_block) {
  724. uint32_t result = header_block.terminator()->GetSingleWordInOperand(1);
  725. auto postdominator_analysis =
  726. ir_context->GetPostDominatorAnalysis(header_block.GetParent());
  727. while (!postdominator_analysis->Dominates(result, header_block.id())) {
  728. auto current_block = ir_context->get_instr_block(result);
  729. // If the transformation is applicable, the terminator is OpBranch.
  730. result = current_block->terminator()->GetSingleWordInOperand(0);
  731. }
  732. return result;
  733. }
  734. bool TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
  735. opt::IRContext* ir_context) {
  736. switch (ir_context->grammar().target_env()) {
  737. case SPV_ENV_UNIVERSAL_1_0:
  738. case SPV_ENV_UNIVERSAL_1_1:
  739. case SPV_ENV_UNIVERSAL_1_2:
  740. case SPV_ENV_UNIVERSAL_1_3:
  741. case SPV_ENV_VULKAN_1_0:
  742. case SPV_ENV_VULKAN_1_1: {
  743. return true;
  744. }
  745. default:
  746. return false;
  747. }
  748. }
  749. void TransformationFlattenConditionalBranch::AddBooleanVectorConstructorToBlock(
  750. uint32_t fresh_id, uint32_t dimension,
  751. const opt::Operand& branch_condition_operand, opt::IRContext* ir_context,
  752. opt::BasicBlock* block) {
  753. opt::Instruction::OperandList in_operands;
  754. for (uint32_t i = 0; i < dimension; i++) {
  755. in_operands.emplace_back(branch_condition_operand);
  756. }
  757. block->begin()->InsertBefore(MakeUnique<opt::Instruction>(
  758. ir_context, spv::Op::OpCompositeConstruct,
  759. fuzzerutil::MaybeGetVectorType(
  760. ir_context, fuzzerutil::MaybeGetBoolType(ir_context), dimension),
  761. fresh_id, in_operands));
  762. fuzzerutil::UpdateModuleIdBound(ir_context, fresh_id);
  763. }
  764. void TransformationFlattenConditionalBranch::
  765. RewriteOpPhiInstructionsAtConvergenceBlock(
  766. const opt::BasicBlock& header_block, uint32_t convergence_block_id,
  767. opt::IRContext* ir_context) const {
  768. const opt::Instruction& branch_instruction = *header_block.terminator();
  769. const opt::Operand& branch_condition_operand =
  770. branch_instruction.GetInOperand(0);
  771. // If we encounter OpPhi instructions on vector types then we may need to
  772. // introduce vector versions of the selection construct's condition to use
  773. // in corresponding OpSelect instructions. These booleans track whether we
  774. // need to introduce such boolean vectors.
  775. bool require_2d_boolean_vector = false;
  776. bool require_3d_boolean_vector = false;
  777. bool require_4d_boolean_vector = false;
  778. // Consider every OpPhi instruction at the convergence block.
  779. opt::BasicBlock* convergence_block =
  780. ir_context->get_instr_block(convergence_block_id);
  781. convergence_block->ForEachPhiInst(
  782. [this, &branch_condition_operand, branch_instruction,
  783. convergence_block_id, &header_block, ir_context,
  784. &require_2d_boolean_vector, &require_3d_boolean_vector,
  785. &require_4d_boolean_vector](opt::Instruction* phi_inst) {
  786. assert(phi_inst->NumInOperands() == 4 &&
  787. "We are going to replace an OpPhi with an OpSelect. This "
  788. "only makes sense if the block has two distinct "
  789. "predecessors.");
  790. // We are going to replace the OpPhi with an OpSelect. By default,
  791. // the condition for the OpSelect will be the branch condition's
  792. // operand. However, if the OpPhi has vector result type we may need
  793. // to use a boolean vector as the condition instead.
  794. opt::Operand selector_operand = branch_condition_operand;
  795. opt::Instruction* type_inst =
  796. ir_context->get_def_use_mgr()->GetDef(phi_inst->type_id());
  797. if (type_inst->opcode() == spv::Op::OpTypeVector) {
  798. uint32_t dimension = type_inst->GetSingleWordInOperand(1);
  799. switch (dimension) {
  800. case 2:
  801. // The OpPhi's result type is a 2D vector. If a fresh id for a
  802. // bvec2 selector was provided then we should use it as the
  803. // OpSelect's condition, and note the fact that we will need to
  804. // add an instruction to bring this bvec2 into existence.
  805. if (message_.fresh_id_for_bvec2_selector() != 0) {
  806. selector_operand = {SPV_OPERAND_TYPE_ID,
  807. {message_.fresh_id_for_bvec2_selector()}};
  808. require_2d_boolean_vector = true;
  809. }
  810. break;
  811. case 3:
  812. // Similar to the 2D case.
  813. if (message_.fresh_id_for_bvec3_selector() != 0) {
  814. selector_operand = {SPV_OPERAND_TYPE_ID,
  815. {message_.fresh_id_for_bvec3_selector()}};
  816. require_3d_boolean_vector = true;
  817. }
  818. break;
  819. case 4:
  820. // Similar to the 2D case.
  821. if (message_.fresh_id_for_bvec4_selector() != 0) {
  822. selector_operand = {SPV_OPERAND_TYPE_ID,
  823. {message_.fresh_id_for_bvec4_selector()}};
  824. require_4d_boolean_vector = true;
  825. }
  826. break;
  827. default:
  828. assert(dimension == 4 && "Invalid vector dimension.");
  829. break;
  830. }
  831. }
  832. std::vector<opt::Operand> operands;
  833. operands.emplace_back(selector_operand);
  834. uint32_t branch_instruction_true_block_id =
  835. branch_instruction.GetSingleWordInOperand(1);
  836. uint32_t branch_instruction_false_block_id =
  837. branch_instruction.GetSingleWordInOperand(2);
  838. // The OpPhi takes values from two distinct predecessors. One
  839. // predecessor is associated with the "true" path of the conditional
  840. // we are flattening, the other with the "false" path, but these
  841. // predecessors can appear in either order as operands to the OpPhi
  842. // instruction. We determine in which order the OpPhi inputs should
  843. // appear as OpSelect arguments by first checking whether the
  844. // convergence block is a direct successor of the selection header, and
  845. // otherwise checking dominance of the true and false immediate
  846. // successors of the header block.
  847. if (branch_instruction_true_block_id == convergence_block_id) {
  848. // The branch instruction's true block is the convergence block. This
  849. // means that the OpPhi's value associated with the branch
  850. // instruction's block should the "true" result of the OpSelect.
  851. assert(branch_instruction_false_block_id != convergence_block_id &&
  852. "Control should not reach here if both branches target the "
  853. "convergence block.");
  854. if (phi_inst->GetSingleWordInOperand(1) ==
  855. message_.header_block_id()) {
  856. operands.emplace_back(phi_inst->GetInOperand(0));
  857. operands.emplace_back(phi_inst->GetInOperand(2));
  858. } else {
  859. assert(phi_inst->GetSingleWordInOperand(3) ==
  860. message_.header_block_id() &&
  861. "Since the convergence block has the header block as one of "
  862. "two predecessors, if it is not handled by the first pair "
  863. "of operands of this OpPhi instruction it should be handled "
  864. "by the second pair.");
  865. operands.emplace_back(phi_inst->GetInOperand(2));
  866. operands.emplace_back(phi_inst->GetInOperand(0));
  867. }
  868. } else if (branch_instruction_false_block_id == convergence_block_id) {
  869. // The branch instruction's false block is the convergence block. This
  870. // means that the OpPhi's value associated with the branch
  871. // instruction's block should the "false" result of the OpSelect.
  872. if (phi_inst->GetSingleWordInOperand(1) ==
  873. message_.header_block_id()) {
  874. operands.emplace_back(phi_inst->GetInOperand(2));
  875. operands.emplace_back(phi_inst->GetInOperand(0));
  876. } else {
  877. assert(phi_inst->GetSingleWordInOperand(3) ==
  878. message_.header_block_id() &&
  879. "Since the convergence block has the header block as one of "
  880. "two predecessors, if it is not handled by the first pair "
  881. "of operands of this OpPhi instruction it should be handled "
  882. "by the second pair.");
  883. operands.emplace_back(phi_inst->GetInOperand(0));
  884. operands.emplace_back(phi_inst->GetInOperand(2));
  885. }
  886. } else if (ir_context->GetDominatorAnalysis(header_block.GetParent())
  887. ->Dominates(branch_instruction_true_block_id,
  888. phi_inst->GetSingleWordInOperand(1))) {
  889. // The "true" branch of the conditional is handled first in the
  890. // OpPhi's operands; we thus provide operands to OpSelect in the same
  891. // order that they appear in the OpPhi.
  892. operands.emplace_back(phi_inst->GetInOperand(0));
  893. operands.emplace_back(phi_inst->GetInOperand(2));
  894. } else {
  895. // The "false" branch of the conditional is handled first in the
  896. // OpPhi's operands; we thus provide operands to OpSelect in reverse
  897. // of the order that they appear in the OpPhi.
  898. operands.emplace_back(phi_inst->GetInOperand(2));
  899. operands.emplace_back(phi_inst->GetInOperand(0));
  900. }
  901. phi_inst->SetOpcode(spv::Op::OpSelect);
  902. phi_inst->SetInOperands(std::move(operands));
  903. });
  904. // Add boolean vector instructions to the start of the block as required.
  905. if (require_2d_boolean_vector) {
  906. AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec2_selector(),
  907. 2, branch_condition_operand, ir_context,
  908. convergence_block);
  909. }
  910. if (require_3d_boolean_vector) {
  911. AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec3_selector(),
  912. 3, branch_condition_operand, ir_context,
  913. convergence_block);
  914. }
  915. if (require_4d_boolean_vector) {
  916. AddBooleanVectorConstructorToBlock(message_.fresh_id_for_bvec4_selector(),
  917. 4, branch_condition_operand, ir_context,
  918. convergence_block);
  919. }
  920. }
  921. } // namespace fuzz
  922. } // namespace spvtools