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