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
15 #include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18
19 namespace spvtools {
20 namespace fuzz {
21 TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(protobufs::TransformationReplaceOpSelectWithConditionalBranch message)22 TransformationReplaceOpSelectWithConditionalBranch(
23 protobufs::TransformationReplaceOpSelectWithConditionalBranch message)
24 : message_(std::move(message)) {}
25
26 TransformationReplaceOpSelectWithConditionalBranch::
TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,uint32_t true_block_id,uint32_t false_block_id)27 TransformationReplaceOpSelectWithConditionalBranch(
28 uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
29 message_.set_select_id(select_id);
30 message_.set_true_block_id(true_block_id);
31 message_.set_false_block_id(false_block_id);
32 }
33
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const34 bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
35 opt::IRContext* ir_context,
36 const TransformationContext& /* unused */) const {
37 assert((message_.true_block_id() || message_.false_block_id()) &&
38 "At least one of the ids must be non-zero.");
39
40 // Check that the non-zero ids are fresh.
41 std::set<uint32_t> used_ids;
42 for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
43 if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
44 &used_ids)) {
45 return false;
46 }
47 }
48
49 auto instruction =
50 ir_context->get_def_use_mgr()->GetDef(message_.select_id());
51
52 // The instruction must exist and it must be an OpSelect instruction.
53 if (!instruction || instruction->opcode() != SpvOpSelect) {
54 return false;
55 }
56
57 // Check that the condition is a scalar boolean.
58 auto condition = ir_context->get_def_use_mgr()->GetDef(
59 instruction->GetSingleWordInOperand(0));
60 assert(condition && "The condition should always exist in a valid module.");
61
62 auto condition_type =
63 ir_context->get_type_mgr()->GetType(condition->type_id());
64 if (!condition_type->AsBool()) {
65 return false;
66 }
67
68 auto block = ir_context->get_instr_block(instruction);
69 assert(block && "The block containing the instruction must be found");
70
71 // The instruction must be the first in its block.
72 if (instruction->unique_id() != block->begin()->unique_id()) {
73 return false;
74 }
75
76 // The block must not be a merge block.
77 if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
78 return false;
79 }
80
81 // The block must have exactly one predecessor.
82 auto predecessors = ir_context->cfg()->preds(block->id());
83 if (predecessors.size() != 1) {
84 return false;
85 }
86
87 uint32_t pred_id = predecessors[0];
88 auto predecessor = ir_context->get_instr_block(pred_id);
89
90 // The predecessor must not be the header of a construct and it must end with
91 // OpBranch.
92 if (predecessor->GetMergeInst() != nullptr ||
93 predecessor->terminator()->opcode() != SpvOpBranch) {
94 return false;
95 }
96
97 return true;
98 }
99
Apply(opt::IRContext * ir_context,TransformationContext *) const100 void TransformationReplaceOpSelectWithConditionalBranch::Apply(
101 opt::IRContext* ir_context, TransformationContext* /* unused */) const {
102 auto instruction =
103 ir_context->get_def_use_mgr()->GetDef(message_.select_id());
104
105 auto block = ir_context->get_instr_block(instruction);
106
107 auto predecessor =
108 ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
109
110 // Create a new block for each non-zero id in {|message_.true_branch_id|,
111 // |message_.false_branch_id|}. Make each newly-created block branch
112 // unconditionally to the instruction block.
113 for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
114 if (id) {
115 fuzzerutil::UpdateModuleIdBound(ir_context, id);
116
117 // Create the new block.
118 auto new_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
119 ir_context, SpvOpLabel, 0, id, opt::Instruction::OperandList{}));
120
121 // Add an unconditional branch from the new block to the instruction
122 // block.
123 new_block->AddInstruction(MakeUnique<opt::Instruction>(
124 ir_context, SpvOpBranch, 0, 0,
125 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
126
127 // Insert the new block right after the predecessor of the instruction
128 // block.
129 block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
130 }
131 }
132
133 // Delete the OpBranch instruction from the predecessor.
134 ir_context->KillInst(predecessor->terminator());
135
136 // Add an OpSelectionMerge instruction to the predecessor block, where the
137 // merge block is the instruction block.
138 predecessor->AddInstruction(MakeUnique<opt::Instruction>(
139 ir_context, SpvOpSelectionMerge, 0, 0,
140 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}},
141 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
142 {SpvSelectionControlMaskNone}}}));
143
144 // |if_block| will be the true block, if it has been created, the instruction
145 // block otherwise.
146 uint32_t if_block =
147 message_.true_block_id() ? message_.true_block_id() : block->id();
148
149 // |else_block| will be the false block, if it has been created, the
150 // instruction block otherwise.
151 uint32_t else_block =
152 message_.false_block_id() ? message_.false_block_id() : block->id();
153
154 assert(if_block != else_block &&
155 "|if_block| and |else_block| should always be different, if the "
156 "transformation is applicable.");
157
158 // Add a conditional branching instruction to the predecessor, branching to
159 // |if_block| if the condition is true and to |if_false| otherwise.
160 predecessor->AddInstruction(MakeUnique<opt::Instruction>(
161 ir_context, SpvOpBranchConditional, 0, 0,
162 opt::Instruction::OperandList{
163 {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
164 {SPV_OPERAND_TYPE_ID, {if_block}},
165 {SPV_OPERAND_TYPE_ID, {else_block}}}));
166
167 // |if_pred| will be the true block, if it has been created, the existing
168 // predecessor otherwise.
169 uint32_t if_pred =
170 message_.true_block_id() ? message_.true_block_id() : predecessor->id();
171
172 // |else_pred| will be the false block, if it has been created, the existing
173 // predecessor otherwise.
174 uint32_t else_pred =
175 message_.false_block_id() ? message_.false_block_id() : predecessor->id();
176
177 // Replace the OpSelect instruction in the merge block with an OpPhi.
178 // This: OpSelect %type %cond %if %else
179 // will become: OpPhi %type %if %if_pred %else %else_pred
180 instruction->SetOpcode(SpvOpPhi);
181 std::vector<opt::Operand> operands;
182
183 operands.emplace_back(instruction->GetInOperand(1));
184 operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
185
186 operands.emplace_back(instruction->GetInOperand(2));
187 operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
188
189 instruction->SetInOperands(std::move(operands));
190
191 // Invalidate all analyses, since the structure of the module was changed.
192 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
193 }
194
195 protobufs::Transformation
ToMessage() const196 TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
197 protobufs::Transformation result;
198 *result.mutable_replace_opselect_with_conditional_branch() = message_;
199 return result;
200 }
201
202 std::unordered_set<uint32_t>
GetFreshIds() const203 TransformationReplaceOpSelectWithConditionalBranch::GetFreshIds() const {
204 return {message_.true_block_id(), message_.false_block_id()};
205 }
206
207 } // namespace fuzz
208 } // namespace spvtools
209