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() != spv::Op::OpSelect) {
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() != spv::Op::OpBranch) {
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>(
119 MakeUnique<opt::Instruction>(ir_context, spv::Op::OpLabel, 0, id,
120 opt::Instruction::OperandList{}));
121
122 // Add an unconditional branch from the new block to the instruction
123 // block.
124 new_block->AddInstruction(MakeUnique<opt::Instruction>(
125 ir_context, spv::Op::OpBranch, 0, 0,
126 opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
127
128 // Insert the new block right after the predecessor of the instruction
129 // block.
130 block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
131 }
132 }
133
134 // Delete the OpBranch instruction from the predecessor.
135 ir_context->KillInst(predecessor->terminator());
136
137 // Add an OpSelectionMerge instruction to the predecessor block, where the
138 // merge block is the instruction block.
139 predecessor->AddInstruction(MakeUnique<opt::Instruction>(
140 ir_context, spv::Op::OpSelectionMerge, 0, 0,
141 opt::Instruction::OperandList{
142 {SPV_OPERAND_TYPE_ID, {block->id()}},
143 {SPV_OPERAND_TYPE_SELECTION_CONTROL,
144 {uint32_t(spv::SelectionControlMask::MaskNone)}}}));
145
146 // |if_block| will be the true block, if it has been created, the instruction
147 // block otherwise.
148 uint32_t if_block =
149 message_.true_block_id() ? message_.true_block_id() : block->id();
150
151 // |else_block| will be the false block, if it has been created, the
152 // instruction block otherwise.
153 uint32_t else_block =
154 message_.false_block_id() ? message_.false_block_id() : block->id();
155
156 assert(if_block != else_block &&
157 "|if_block| and |else_block| should always be different, if the "
158 "transformation is applicable.");
159
160 // Add a conditional branching instruction to the predecessor, branching to
161 // |if_block| if the condition is true and to |if_false| otherwise.
162 predecessor->AddInstruction(MakeUnique<opt::Instruction>(
163 ir_context, spv::Op::OpBranchConditional, 0, 0,
164 opt::Instruction::OperandList{
165 {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
166 {SPV_OPERAND_TYPE_ID, {if_block}},
167 {SPV_OPERAND_TYPE_ID, {else_block}}}));
168
169 // |if_pred| will be the true block, if it has been created, the existing
170 // predecessor otherwise.
171 uint32_t if_pred =
172 message_.true_block_id() ? message_.true_block_id() : predecessor->id();
173
174 // |else_pred| will be the false block, if it has been created, the existing
175 // predecessor otherwise.
176 uint32_t else_pred =
177 message_.false_block_id() ? message_.false_block_id() : predecessor->id();
178
179 // Replace the OpSelect instruction in the merge block with an OpPhi.
180 // This: OpSelect %type %cond %if %else
181 // will become: OpPhi %type %if %if_pred %else %else_pred
182 instruction->SetOpcode(spv::Op::OpPhi);
183 std::vector<opt::Operand> operands;
184
185 operands.emplace_back(instruction->GetInOperand(1));
186 operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
187
188 operands.emplace_back(instruction->GetInOperand(2));
189 operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
190
191 instruction->SetInOperands(std::move(operands));
192
193 // Invalidate all analyses, since the structure of the module was changed.
194 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
195 }
196
197 protobufs::Transformation
ToMessage() const198 TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
199 protobufs::Transformation result;
200 *result.mutable_replace_opselect_with_conditional_branch() = message_;
201 return result;
202 }
203
204 std::unordered_set<uint32_t>
GetFreshIds() const205 TransformationReplaceOpSelectWithConditionalBranch::GetFreshIds() const {
206 return {message_.true_block_id(), message_.false_block_id()};
207 }
208
209 } // namespace fuzz
210 } // namespace spvtools
211