1 // Copyright (c) 2020 André Perez Maselco
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_bit_instruction_synonym.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19
20 namespace spvtools {
21 namespace fuzz {
22
TransformationAddBitInstructionSynonym(protobufs::TransformationAddBitInstructionSynonym message)23 TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
24 protobufs::TransformationAddBitInstructionSynonym message)
25 : message_(std::move(message)) {}
26
TransformationAddBitInstructionSynonym(const uint32_t instruction_result_id,const std::vector<uint32_t> & fresh_ids)27 TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym(
28 const uint32_t instruction_result_id,
29 const std::vector<uint32_t>& fresh_ids) {
30 message_.set_instruction_result_id(instruction_result_id);
31 *message_.mutable_fresh_ids() =
32 google::protobuf::RepeatedField<google::protobuf::uint32>(
33 fresh_ids.begin(), fresh_ids.end());
34 }
35
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const36 bool TransformationAddBitInstructionSynonym::IsApplicable(
37 opt::IRContext* ir_context,
38 const TransformationContext& transformation_context) const {
39 auto instruction =
40 ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
41
42 // Checks on: only integer operands are supported, instructions are bitwise
43 // operations only. Signedness of the operands must be the same.
44 if (!IsInstructionSupported(ir_context, instruction)) {
45 return false;
46 }
47
48 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3791):
49 // This condition could be relaxed if the index exists as another integer
50 // type.
51 // All bit indexes must be defined as 32-bit unsigned integers.
52 uint32_t width = ir_context->get_type_mgr()
53 ->GetType(instruction->type_id())
54 ->AsInteger()
55 ->width();
56 for (uint32_t i = 0; i < width; i++) {
57 if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context,
58 {i}, 32, false, false)) {
59 return false;
60 }
61 }
62
63 // |message_.fresh_ids.size| must have the exact number of fresh ids required
64 // to apply the transformation.
65 if (static_cast<uint32_t>(message_.fresh_ids().size()) !=
66 GetRequiredFreshIdCount(ir_context, instruction)) {
67 return false;
68 }
69
70 // All ids in |message_.fresh_ids| must be fresh.
71 for (uint32_t fresh_id : message_.fresh_ids()) {
72 if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) {
73 return false;
74 }
75 }
76
77 return true;
78 }
79
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const80 void TransformationAddBitInstructionSynonym::Apply(
81 opt::IRContext* ir_context,
82 TransformationContext* transformation_context) const {
83 auto bit_instruction =
84 ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
85
86 // Use an appropriate helper function to add the new instruction and new
87 // synonym fact. The helper function should take care of invalidating
88 // analyses before adding facts.
89 switch (bit_instruction->opcode()) {
90 case SpvOpBitwiseOr:
91 case SpvOpBitwiseXor:
92 case SpvOpBitwiseAnd:
93 case SpvOpNot:
94 AddOpBitwiseOrOpNotSynonym(ir_context, transformation_context,
95 bit_instruction);
96 break;
97 default:
98 assert(false && "Should be unreachable.");
99 break;
100 }
101 }
102
IsInstructionSupported(opt::IRContext * ir_context,opt::Instruction * instruction)103 bool TransformationAddBitInstructionSynonym::IsInstructionSupported(
104 opt::IRContext* ir_context, opt::Instruction* instruction) {
105 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
106 // Right now we only support certain operations. When this issue is addressed
107 // the following conditional can use the function |spvOpcodeIsBit|.
108 // |instruction| must be defined and must be a supported bit instruction.
109 if (!instruction || (instruction->opcode() != SpvOpBitwiseOr &&
110 instruction->opcode() != SpvOpBitwiseXor &&
111 instruction->opcode() != SpvOpBitwiseAnd &&
112 instruction->opcode() != SpvOpNot)) {
113 return false;
114 }
115
116 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3792):
117 // Right now, only integer operands are supported.
118 if (ir_context->get_type_mgr()->GetType(instruction->type_id())->AsVector()) {
119 return false;
120 }
121
122 if (instruction->opcode() == SpvOpNot) {
123 auto operand = instruction->GetInOperand(0).words[0];
124 auto operand_inst = ir_context->get_def_use_mgr()->GetDef(operand);
125 auto operand_type =
126 ir_context->get_type_mgr()->GetType(operand_inst->type_id());
127 auto operand_sign = operand_type->AsInteger()->IsSigned();
128
129 auto type_id_sign = ir_context->get_type_mgr()
130 ->GetType(instruction->type_id())
131 ->AsInteger()
132 ->IsSigned();
133
134 return operand_sign == type_id_sign;
135
136 } else {
137 // Other BitWise operations that takes two operands.
138 auto first_operand = instruction->GetInOperand(0).words[0];
139 auto first_operand_inst =
140 ir_context->get_def_use_mgr()->GetDef(first_operand);
141 auto first_operand_type =
142 ir_context->get_type_mgr()->GetType(first_operand_inst->type_id());
143 auto first_operand_sign = first_operand_type->AsInteger()->IsSigned();
144
145 auto second_operand = instruction->GetInOperand(1).words[0];
146 auto second_operand_inst =
147 ir_context->get_def_use_mgr()->GetDef(second_operand);
148 auto second_operand_type =
149 ir_context->get_type_mgr()->GetType(second_operand_inst->type_id());
150 auto second_operand_sign = second_operand_type->AsInteger()->IsSigned();
151
152 auto type_id_sign = ir_context->get_type_mgr()
153 ->GetType(instruction->type_id())
154 ->AsInteger()
155 ->IsSigned();
156
157 return first_operand_sign == second_operand_sign &&
158 first_operand_sign == type_id_sign;
159 }
160 }
161
ToMessage() const162 protobufs::Transformation TransformationAddBitInstructionSynonym::ToMessage()
163 const {
164 protobufs::Transformation result;
165 *result.mutable_add_bit_instruction_synonym() = message_;
166 return result;
167 }
168
GetRequiredFreshIdCount(opt::IRContext * ir_context,opt::Instruction * bit_instruction)169 uint32_t TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount(
170 opt::IRContext* ir_context, opt::Instruction* bit_instruction) {
171 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557):
172 // Right now, only certain operations are supported.
173 switch (bit_instruction->opcode()) {
174 case SpvOpBitwiseOr:
175 case SpvOpBitwiseXor:
176 case SpvOpBitwiseAnd:
177 case SpvOpNot:
178 return (2 + bit_instruction->NumInOperands()) *
179 ir_context->get_type_mgr()
180 ->GetType(bit_instruction->type_id())
181 ->AsInteger()
182 ->width() -
183 1;
184 default:
185 assert(false && "Unsupported bit instruction.");
186 return 0;
187 }
188 }
189
AddOpBitwiseOrOpNotSynonym(opt::IRContext * ir_context,TransformationContext * transformation_context,opt::Instruction * bit_instruction) const190 void TransformationAddBitInstructionSynonym::AddOpBitwiseOrOpNotSynonym(
191 opt::IRContext* ir_context, TransformationContext* transformation_context,
192 opt::Instruction* bit_instruction) const {
193 // Fresh id iterator.
194 auto fresh_id = message_.fresh_ids().begin();
195
196 // |width| is the bit width of operands (8, 16, 32 or 64).
197 const uint32_t width = ir_context->get_type_mgr()
198 ->GetType(bit_instruction->type_id())
199 ->AsInteger()
200 ->width();
201
202 // |count| is the number of bits to be extracted and inserted at a time.
203 const uint32_t count = fuzzerutil::MaybeGetIntegerConstant(
204 ir_context, *transformation_context, {1}, 32, false, false);
205
206 // |extracted_bit_instructions| is the collection of OpBiwise* or OpNot
207 // instructions that evaluate the extracted bits. Those ids will be used to
208 // insert the result bits.
209 std::vector<uint32_t> extracted_bit_instructions(width);
210
211 for (uint32_t i = 0; i < width; i++) {
212 // |offset| is the current bit index.
213 uint32_t offset = fuzzerutil::MaybeGetIntegerConstant(
214 ir_context, *transformation_context, {i}, 32, false, false);
215
216 // |bit_extract_ids| are the two extracted bits from the operands.
217 opt::Instruction::OperandList bit_extract_ids;
218
219 // Extracts the i-th bit from operands.
220 for (auto operand = bit_instruction->begin() + 2;
221 operand != bit_instruction->end(); operand++) {
222 auto bit_extract =
223 opt::Instruction(ir_context, SpvOpBitFieldUExtract,
224 bit_instruction->type_id(), *fresh_id++,
225 {{SPV_OPERAND_TYPE_ID, operand->words},
226 {SPV_OPERAND_TYPE_ID, {offset}},
227 {SPV_OPERAND_TYPE_ID, {count}}});
228 bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_extract));
229 fuzzerutil::UpdateModuleIdBound(ir_context, bit_extract.result_id());
230 bit_extract_ids.push_back(
231 {SPV_OPERAND_TYPE_ID, {bit_extract.result_id()}});
232 }
233
234 // Applies |bit_instruction| to the extracted bits.
235 auto extracted_bit_instruction = opt::Instruction(
236 ir_context, bit_instruction->opcode(), bit_instruction->type_id(),
237 *fresh_id++, bit_extract_ids);
238 bit_instruction->InsertBefore(
239 MakeUnique<opt::Instruction>(extracted_bit_instruction));
240 fuzzerutil::UpdateModuleIdBound(ir_context,
241 extracted_bit_instruction.result_id());
242 extracted_bit_instructions[i] = extracted_bit_instruction.result_id();
243 }
244
245 // The first two ids in |extracted_bit_instructions| are used to insert the
246 // first two bits of the result.
247 uint32_t offset = fuzzerutil::MaybeGetIntegerConstant(
248 ir_context, *transformation_context, {1}, 32, false, false);
249 auto bit_insert = opt::Instruction(
250 ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(), *fresh_id++,
251 {{SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[0]}},
252 {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[1]}},
253 {SPV_OPERAND_TYPE_ID, {offset}},
254 {SPV_OPERAND_TYPE_ID, {count}}});
255 bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert));
256 fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id());
257
258 // Inserts the remaining bits.
259 for (uint32_t i = 2; i < width; i++) {
260 offset = fuzzerutil::MaybeGetIntegerConstant(
261 ir_context, *transformation_context, {i}, 32, false, false);
262 bit_insert = opt::Instruction(
263 ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(),
264 *fresh_id++,
265 {{SPV_OPERAND_TYPE_ID, {bit_insert.result_id()}},
266 {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[i]}},
267 {SPV_OPERAND_TYPE_ID, {offset}},
268 {SPV_OPERAND_TYPE_ID, {count}}});
269 bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert));
270 fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id());
271 }
272
273 ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
274
275 // We only add a synonym fact if the bit instruction is not irrelevant, and if
276 // the new result id we would make it synonymous with is not irrelevant. (It
277 // could be irrelevant if we are in a dead block.)
278 if (!transformation_context->GetFactManager()->IdIsIrrelevant(
279 bit_instruction->result_id()) &&
280 !transformation_context->GetFactManager()->IdIsIrrelevant(
281 bit_insert.result_id())) {
282 // Adds the fact that the last |bit_insert| instruction is synonymous of
283 // |bit_instruction|.
284 transformation_context->GetFactManager()->AddFactDataSynonym(
285 MakeDataDescriptor(bit_insert.result_id(), {}),
286 MakeDataDescriptor(bit_instruction->result_id(), {}));
287 }
288 }
289
290 std::unordered_set<uint32_t>
GetFreshIds() const291 TransformationAddBitInstructionSynonym::GetFreshIds() const {
292 std::unordered_set<uint32_t> result;
293 for (auto id : message_.fresh_ids()) {
294 result.insert(id);
295 }
296 return result;
297 }
298
299 } // namespace fuzz
300 } // namespace spvtools
301