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