transformation_add_dead_break.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  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_add_dead_break.h"
  15. #include "source/fuzz/fuzzer_util.h"
  16. #include "source/fuzz/transformation_context.h"
  17. #include "source/opt/basic_block.h"
  18. #include "source/opt/ir_context.h"
  19. #include "source/opt/struct_cfg_analysis.h"
  20. namespace spvtools {
  21. namespace fuzz {
  22. TransformationAddDeadBreak::TransformationAddDeadBreak(
  23. protobufs::TransformationAddDeadBreak message)
  24. : message_(std::move(message)) {}
  25. TransformationAddDeadBreak::TransformationAddDeadBreak(
  26. uint32_t from_block, uint32_t to_block, bool break_condition_value,
  27. std::vector<uint32_t> phi_id) {
  28. message_.set_from_block(from_block);
  29. message_.set_to_block(to_block);
  30. message_.set_break_condition_value(break_condition_value);
  31. for (auto id : phi_id) {
  32. message_.add_phi_id(id);
  33. }
  34. }
  35. bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow(
  36. opt::IRContext* ir_context, opt::BasicBlock* bb_from) const {
  37. // Look at the structured control flow associated with |from_block| and
  38. // check whether it is contained in an appropriate construct with merge id
  39. // |to_block| such that a break from |from_block| to |to_block| is legal.
  40. // There are three legal cases to consider:
  41. // (1) |from_block| is a loop header and |to_block| is its merge
  42. // (2) |from_block| is a non-header node of a construct, and |to_block|
  43. // is the merge for that construct
  44. // (3) |from_block| is a non-header node of a selection construct, and
  45. // |to_block| is the merge for the innermost loop containing
  46. // |from_block|
  47. //
  48. // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2653) It may be
  49. // possible to be more aggressive in breaking from switch constructs.
  50. //
  51. // The reason we need to distinguish between cases (1) and (2) is that the
  52. // structured CFG analysis does not deem a header to be part of the construct
  53. // that it heads.
  54. // Consider case (1)
  55. if (bb_from->IsLoopHeader()) {
  56. // Case (1) holds if |to_block| is the merge block for the loop;
  57. // otherwise no case holds
  58. return bb_from->MergeBlockId() == message_.to_block();
  59. }
  60. // Both cases (2) and (3) require that |from_block| is inside some
  61. // structured control flow construct.
  62. auto containing_construct =
  63. ir_context->GetStructuredCFGAnalysis()->ContainingConstruct(
  64. message_.from_block());
  65. if (!containing_construct) {
  66. // |from_block| is not in a construct from which we can break.
  67. return false;
  68. }
  69. // Consider case (2)
  70. if (message_.to_block() ==
  71. ir_context->cfg()->block(containing_construct)->MergeBlockId()) {
  72. // This looks like an instance of case (2).
  73. // However, the structured CFG analysis regards the continue construct of a
  74. // loop as part of the loop, but it is not legal to jump from a loop's
  75. // continue construct to the loop's merge (except from the back-edge block),
  76. // so we need to check for this case.
  77. return !fuzzerutil::BlockIsInLoopContinueConstruct(
  78. ir_context, message_.from_block(), containing_construct) ||
  79. fuzzerutil::BlockIsBackEdge(ir_context, message_.from_block(),
  80. containing_construct);
  81. }
  82. // Case (3) holds if and only if |to_block| is the merge block for this
  83. // innermost loop that contains |from_block|
  84. auto containing_loop_header =
  85. ir_context->GetStructuredCFGAnalysis()->ContainingLoop(
  86. message_.from_block());
  87. if (containing_loop_header &&
  88. message_.to_block() ==
  89. ir_context->cfg()->block(containing_loop_header)->MergeBlockId()) {
  90. return !fuzzerutil::BlockIsInLoopContinueConstruct(
  91. ir_context, message_.from_block(), containing_loop_header) ||
  92. fuzzerutil::BlockIsBackEdge(ir_context, message_.from_block(),
  93. containing_loop_header);
  94. }
  95. return false;
  96. }
  97. bool TransformationAddDeadBreak::IsApplicable(
  98. opt::IRContext* ir_context,
  99. const TransformationContext& transformation_context) const {
  100. // First, we check that a constant with the same value as
  101. // |message_.break_condition_value| is present.
  102. const auto bool_id =
  103. fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
  104. message_.break_condition_value(), false);
  105. if (!bool_id) {
  106. // The required constant is not present, so the transformation cannot be
  107. // applied.
  108. return false;
  109. }
  110. // Check that |message_.from_block| and |message_.to_block| really are block
  111. // ids
  112. opt::BasicBlock* bb_from =
  113. fuzzerutil::MaybeFindBlock(ir_context, message_.from_block());
  114. if (bb_from == nullptr) {
  115. return false;
  116. }
  117. opt::BasicBlock* bb_to =
  118. fuzzerutil::MaybeFindBlock(ir_context, message_.to_block());
  119. if (bb_to == nullptr) {
  120. return false;
  121. }
  122. if (!ir_context->IsReachable(*bb_to)) {
  123. // If the target of the break is unreachable, we conservatively do not
  124. // allow adding a dead break, to avoid the compilations that arise due to
  125. // the lack of sensible dominance information for unreachable blocks.
  126. return false;
  127. }
  128. // Check that |message_.from_block| ends with an unconditional branch.
  129. if (bb_from->terminator()->opcode() != spv::Op::OpBranch) {
  130. // The block associated with the id does not end with an unconditional
  131. // branch.
  132. return false;
  133. }
  134. assert(bb_from != nullptr &&
  135. "We should have found a block if this line of code is reached.");
  136. assert(
  137. bb_from->id() == message_.from_block() &&
  138. "The id of the block we found should match the source id for the break.");
  139. assert(bb_to != nullptr &&
  140. "We should have found a block if this line of code is reached.");
  141. assert(
  142. bb_to->id() == message_.to_block() &&
  143. "The id of the block we found should match the target id for the break.");
  144. // Check whether the data passed to extend OpPhi instructions is appropriate.
  145. if (!fuzzerutil::PhiIdsOkForNewEdge(ir_context, bb_from, bb_to,
  146. message_.phi_id())) {
  147. return false;
  148. }
  149. // Check that adding the break would respect the rules of structured
  150. // control flow.
  151. if (!AddingBreakRespectsStructuredControlFlow(ir_context, bb_from)) {
  152. return false;
  153. }
  154. // Adding the dead break is only valid if SPIR-V rules related to dominance
  155. // hold.
  156. return fuzzerutil::NewTerminatorPreservesDominationRules(
  157. ir_context, message_.from_block(),
  158. fuzzerutil::CreateUnreachableEdgeInstruction(
  159. ir_context, message_.from_block(), message_.to_block(), bool_id));
  160. }
  161. void TransformationAddDeadBreak::Apply(
  162. opt::IRContext* ir_context,
  163. TransformationContext* transformation_context) const {
  164. fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis(
  165. ir_context, ir_context->cfg()->block(message_.from_block()),
  166. ir_context->cfg()->block(message_.to_block()),
  167. fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
  168. message_.break_condition_value(), false),
  169. message_.phi_id());
  170. // Invalidate all analyses
  171. ir_context->InvalidateAnalysesExceptFor(
  172. opt::IRContext::Analysis::kAnalysisNone);
  173. }
  174. protobufs::Transformation TransformationAddDeadBreak::ToMessage() const {
  175. protobufs::Transformation result;
  176. *result.mutable_add_dead_break() = message_;
  177. return result;
  178. }
  179. std::unordered_set<uint32_t> TransformationAddDeadBreak::GetFreshIds() const {
  180. return std::unordered_set<uint32_t>();
  181. }
  182. } // namespace fuzz
  183. } // namespace spvtools