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_block.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 
19 namespace spvtools {
20 namespace fuzz {
21 
TransformationAddDeadBlock(const spvtools::fuzz::protobufs::TransformationAddDeadBlock & message)22 TransformationAddDeadBlock::TransformationAddDeadBlock(
23     const spvtools::fuzz::protobufs::TransformationAddDeadBlock& message)
24     : message_(message) {}
25 
TransformationAddDeadBlock(uint32_t fresh_id,uint32_t existing_block,bool condition_value)26 TransformationAddDeadBlock::TransformationAddDeadBlock(uint32_t fresh_id,
27                                                        uint32_t existing_block,
28                                                        bool condition_value) {
29   message_.set_fresh_id(fresh_id);
30   message_.set_existing_block(existing_block);
31   message_.set_condition_value(condition_value);
32 }
33 
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const34 bool TransformationAddDeadBlock::IsApplicable(
35     opt::IRContext* ir_context,
36     const TransformationContext& transformation_context) const {
37   // The new block's id must be fresh.
38   if (!fuzzerutil::IsFreshId(ir_context, message_.fresh_id())) {
39     return false;
40   }
41 
42   // First, we check that a constant with the same value as
43   // |message_.condition_value| is present.
44   if (!fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
45                                         message_.condition_value(), false)) {
46     // The required constant is not present, so the transformation cannot be
47     // applied.
48     return false;
49   }
50 
51   // The existing block must indeed exist.
52   auto existing_block =
53       fuzzerutil::MaybeFindBlock(ir_context, message_.existing_block());
54   if (!existing_block) {
55     return false;
56   }
57 
58   // It must not head a loop.
59   if (existing_block->IsLoopHeader()) {
60     return false;
61   }
62 
63   // It must end with OpBranch.
64   if (existing_block->terminator()->opcode() != SpvOpBranch) {
65     return false;
66   }
67 
68   // Its successor must not be a merge block nor continue target.
69   auto successor_block_id =
70       existing_block->terminator()->GetSingleWordInOperand(0);
71   if (fuzzerutil::IsMergeOrContinue(ir_context, successor_block_id)) {
72     return false;
73   }
74 
75   // The successor must not be a loop header (i.e., |message_.existing_block|
76   // must not be a back-edge block.
77   if (ir_context->cfg()->block(successor_block_id)->IsLoopHeader()) {
78     return false;
79   }
80 
81   // |existing_block| must be reachable.
82   opt::DominatorAnalysis* dominator_analysis =
83       ir_context->GetDominatorAnalysis(existing_block->GetParent());
84   if (!dominator_analysis->IsReachable(existing_block->id())) {
85     return false;
86   }
87 
88   assert(existing_block->id() != successor_block_id &&
89          "|existing_block| must be different from |successor_block_id|");
90 
91   // Even though we know |successor_block_id| is not a merge block, it might
92   // still have multiple predecessors because divergent control flow is allowed
93   // to converge early (before the merge block). In this case, when we create
94   // the selection construct, its header |existing_block| will not dominate the
95   // merge block |successor_block_id|, which is invalid. Thus, |existing_block|
96   // must dominate |successor_block_id|.
97   if (!dominator_analysis->Dominates(existing_block->id(),
98                                      successor_block_id)) {
99     return false;
100   }
101 
102   return true;
103 }
104 
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const105 void TransformationAddDeadBlock::Apply(
106     opt::IRContext* ir_context,
107     TransformationContext* transformation_context) const {
108   // Update the module id bound so that it is at least the id of the new block.
109   fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
110 
111   // Get the existing block and its successor.
112   auto existing_block = ir_context->cfg()->block(message_.existing_block());
113   auto successor_block_id =
114       existing_block->terminator()->GetSingleWordInOperand(0);
115 
116   // Get the id of the boolean value that will be used as the branch condition.
117   auto bool_id = fuzzerutil::MaybeGetBoolConstant(
118       ir_context, *transformation_context, message_.condition_value(), false);
119 
120   // Make a new block that unconditionally branches to the original successor
121   // block.
122   auto enclosing_function = existing_block->GetParent();
123   std::unique_ptr<opt::BasicBlock> new_block =
124       MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
125           ir_context, SpvOpLabel, 0, message_.fresh_id(),
126           opt::Instruction::OperandList()));
127   new_block->AddInstruction(MakeUnique<opt::Instruction>(
128       ir_context, SpvOpBranch, 0, 0,
129       opt::Instruction::OperandList(
130           {{SPV_OPERAND_TYPE_ID, {successor_block_id}}})));
131 
132   // Turn the original block into a selection merge, with its original successor
133   // as the merge block.
134   existing_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
135       ir_context, SpvOpSelectionMerge, 0, 0,
136       opt::Instruction::OperandList(
137           {{SPV_OPERAND_TYPE_ID, {successor_block_id}},
138            {SPV_OPERAND_TYPE_SELECTION_CONTROL,
139             {SpvSelectionControlMaskNone}}})));
140 
141   // Change the original block's terminator to be a conditional branch on the
142   // given boolean, with the original successor and the new successor as branch
143   // targets, and such that at runtime control will always transfer to the
144   // original successor.
145   existing_block->terminator()->SetOpcode(SpvOpBranchConditional);
146   existing_block->terminator()->SetInOperands(
147       {{SPV_OPERAND_TYPE_ID, {bool_id}},
148        {SPV_OPERAND_TYPE_ID,
149         {message_.condition_value() ? successor_block_id
150                                     : message_.fresh_id()}},
151        {SPV_OPERAND_TYPE_ID,
152         {message_.condition_value() ? message_.fresh_id()
153                                     : successor_block_id}}});
154 
155   // Add the new block to the enclosing function.
156   enclosing_function->InsertBasicBlockAfter(std::move(new_block),
157                                             existing_block);
158 
159   // Fix up OpPhi instructions in the successor block, so that the values they
160   // yield when control has transferred from the new block are the same as if
161   // control had transferred from |message_.existing_block|.  This is guaranteed
162   // to be valid since |message_.existing_block| dominates the new block by
163   // construction.  Other transformations can change these phi operands to more
164   // interesting values.
165   ir_context->cfg()
166       ->block(successor_block_id)
167       ->ForEachPhiInst([this](opt::Instruction* phi_inst) {
168         // Copy the operand that provides the phi value for the first of any
169         // existing predecessors.
170         opt::Operand copy_of_existing_operand = phi_inst->GetInOperand(0);
171         // Use this as the value associated with the new predecessor.
172         phi_inst->AddOperand(std::move(copy_of_existing_operand));
173         phi_inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.fresh_id()}});
174       });
175 
176   // Do not rely on any existing analysis results since the control flow graph
177   // of the module has changed.
178   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
179 
180   // Record the fact that the new block is dead.
181   transformation_context->GetFactManager()->AddFactBlockIsDead(
182       message_.fresh_id());
183 }
184 
ToMessage() const185 protobufs::Transformation TransformationAddDeadBlock::ToMessage() const {
186   protobufs::Transformation result;
187   *result.mutable_add_dead_block() = message_;
188   return result;
189 }
190 
GetFreshIds() const191 std::unordered_set<uint32_t> TransformationAddDeadBlock::GetFreshIds() const {
192   return {message_.fresh_id()};
193 }
194 
195 }  // namespace fuzz
196 }  // namespace spvtools
197