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