| // Copyright (c) 2020 André Perez Maselco |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "source/fuzz/transformation_add_bit_instruction_synonym.h" |
| |
| #include "source/fuzz/fuzzer_util.h" |
| #include "source/fuzz/instruction_descriptor.h" |
| |
| namespace spvtools { |
| namespace fuzz { |
| |
| TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym( |
| protobufs::TransformationAddBitInstructionSynonym message) |
| : message_(std::move(message)) {} |
| |
| TransformationAddBitInstructionSynonym::TransformationAddBitInstructionSynonym( |
| const uint32_t instruction_result_id, |
| const std::vector<uint32_t>& fresh_ids) { |
| message_.set_instruction_result_id(instruction_result_id); |
| *message_.mutable_fresh_ids() = |
| google::protobuf::RepeatedField<google::protobuf::uint32>( |
| fresh_ids.begin(), fresh_ids.end()); |
| } |
| |
| bool TransformationAddBitInstructionSynonym::IsApplicable( |
| opt::IRContext* ir_context, |
| const TransformationContext& transformation_context) const { |
| auto instruction = |
| ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); |
| |
| // Checks on: only integer operands are supported, instructions are bitwise |
| // operations only. Signedness of the operands must be the same. |
| if (!IsInstructionSupported(ir_context, instruction)) { |
| return false; |
| } |
| |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3791): |
| // This condition could be relaxed if the index exists as another integer |
| // type. |
| // All bit indexes must be defined as 32-bit unsigned integers. |
| uint32_t width = ir_context->get_type_mgr() |
| ->GetType(instruction->type_id()) |
| ->AsInteger() |
| ->width(); |
| for (uint32_t i = 0; i < width; i++) { |
| if (!fuzzerutil::MaybeGetIntegerConstant(ir_context, transformation_context, |
| {i}, 32, false, false)) { |
| return false; |
| } |
| } |
| |
| // |message_.fresh_ids.size| must have the exact number of fresh ids required |
| // to apply the transformation. |
| if (static_cast<uint32_t>(message_.fresh_ids().size()) != |
| GetRequiredFreshIdCount(ir_context, instruction)) { |
| return false; |
| } |
| |
| // All ids in |message_.fresh_ids| must be fresh. |
| for (uint32_t fresh_id : message_.fresh_ids()) { |
| if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| void TransformationAddBitInstructionSynonym::Apply( |
| opt::IRContext* ir_context, |
| TransformationContext* transformation_context) const { |
| auto bit_instruction = |
| ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id()); |
| |
| // Use an appropriate helper function to add the new instruction and new |
| // synonym fact. The helper function should take care of invalidating |
| // analyses before adding facts. |
| switch (bit_instruction->opcode()) { |
| case SpvOpBitwiseOr: |
| case SpvOpBitwiseXor: |
| case SpvOpBitwiseAnd: |
| case SpvOpNot: |
| AddOpBitwiseOrOpNotSynonym(ir_context, transformation_context, |
| bit_instruction); |
| break; |
| default: |
| assert(false && "Should be unreachable."); |
| break; |
| } |
| } |
| |
| bool TransformationAddBitInstructionSynonym::IsInstructionSupported( |
| opt::IRContext* ir_context, opt::Instruction* instruction) { |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557): |
| // Right now we only support certain operations. When this issue is addressed |
| // the following conditional can use the function |spvOpcodeIsBit|. |
| // |instruction| must be defined and must be a supported bit instruction. |
| if (!instruction || (instruction->opcode() != SpvOpBitwiseOr && |
| instruction->opcode() != SpvOpBitwiseXor && |
| instruction->opcode() != SpvOpBitwiseAnd && |
| instruction->opcode() != SpvOpNot)) { |
| return false; |
| } |
| |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3792): |
| // Right now, only integer operands are supported. |
| if (ir_context->get_type_mgr()->GetType(instruction->type_id())->AsVector()) { |
| return false; |
| } |
| |
| if (instruction->opcode() == SpvOpNot) { |
| auto operand = instruction->GetInOperand(0).words[0]; |
| auto operand_inst = ir_context->get_def_use_mgr()->GetDef(operand); |
| auto operand_type = |
| ir_context->get_type_mgr()->GetType(operand_inst->type_id()); |
| auto operand_sign = operand_type->AsInteger()->IsSigned(); |
| |
| auto type_id_sign = ir_context->get_type_mgr() |
| ->GetType(instruction->type_id()) |
| ->AsInteger() |
| ->IsSigned(); |
| |
| return operand_sign == type_id_sign; |
| |
| } else { |
| // Other BitWise operations that takes two operands. |
| auto first_operand = instruction->GetInOperand(0).words[0]; |
| auto first_operand_inst = |
| ir_context->get_def_use_mgr()->GetDef(first_operand); |
| auto first_operand_type = |
| ir_context->get_type_mgr()->GetType(first_operand_inst->type_id()); |
| auto first_operand_sign = first_operand_type->AsInteger()->IsSigned(); |
| |
| auto second_operand = instruction->GetInOperand(1).words[0]; |
| auto second_operand_inst = |
| ir_context->get_def_use_mgr()->GetDef(second_operand); |
| auto second_operand_type = |
| ir_context->get_type_mgr()->GetType(second_operand_inst->type_id()); |
| auto second_operand_sign = second_operand_type->AsInteger()->IsSigned(); |
| |
| auto type_id_sign = ir_context->get_type_mgr() |
| ->GetType(instruction->type_id()) |
| ->AsInteger() |
| ->IsSigned(); |
| |
| return first_operand_sign == second_operand_sign && |
| first_operand_sign == type_id_sign; |
| } |
| } |
| |
| protobufs::Transformation TransformationAddBitInstructionSynonym::ToMessage() |
| const { |
| protobufs::Transformation result; |
| *result.mutable_add_bit_instruction_synonym() = message_; |
| return result; |
| } |
| |
| uint32_t TransformationAddBitInstructionSynonym::GetRequiredFreshIdCount( |
| opt::IRContext* ir_context, opt::Instruction* bit_instruction) { |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3557): |
| // Right now, only certain operations are supported. |
| switch (bit_instruction->opcode()) { |
| case SpvOpBitwiseOr: |
| case SpvOpBitwiseXor: |
| case SpvOpBitwiseAnd: |
| case SpvOpNot: |
| return (2 + bit_instruction->NumInOperands()) * |
| ir_context->get_type_mgr() |
| ->GetType(bit_instruction->type_id()) |
| ->AsInteger() |
| ->width() - |
| 1; |
| default: |
| assert(false && "Unsupported bit instruction."); |
| return 0; |
| } |
| } |
| |
| void TransformationAddBitInstructionSynonym::AddOpBitwiseOrOpNotSynonym( |
| opt::IRContext* ir_context, TransformationContext* transformation_context, |
| opt::Instruction* bit_instruction) const { |
| // Fresh id iterator. |
| auto fresh_id = message_.fresh_ids().begin(); |
| |
| // |width| is the bit width of operands (8, 16, 32 or 64). |
| const uint32_t width = ir_context->get_type_mgr() |
| ->GetType(bit_instruction->type_id()) |
| ->AsInteger() |
| ->width(); |
| |
| // |count| is the number of bits to be extracted and inserted at a time. |
| const uint32_t count = fuzzerutil::MaybeGetIntegerConstant( |
| ir_context, *transformation_context, {1}, 32, false, false); |
| |
| // |extracted_bit_instructions| is the collection of OpBiwise* or OpNot |
| // instructions that evaluate the extracted bits. Those ids will be used to |
| // insert the result bits. |
| std::vector<uint32_t> extracted_bit_instructions(width); |
| |
| for (uint32_t i = 0; i < width; i++) { |
| // |offset| is the current bit index. |
| uint32_t offset = fuzzerutil::MaybeGetIntegerConstant( |
| ir_context, *transformation_context, {i}, 32, false, false); |
| |
| // |bit_extract_ids| are the two extracted bits from the operands. |
| opt::Instruction::OperandList bit_extract_ids; |
| |
| // Extracts the i-th bit from operands. |
| for (auto operand = bit_instruction->begin() + 2; |
| operand != bit_instruction->end(); operand++) { |
| auto bit_extract = |
| opt::Instruction(ir_context, SpvOpBitFieldUExtract, |
| bit_instruction->type_id(), *fresh_id++, |
| {{SPV_OPERAND_TYPE_ID, operand->words}, |
| {SPV_OPERAND_TYPE_ID, {offset}}, |
| {SPV_OPERAND_TYPE_ID, {count}}}); |
| bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_extract)); |
| fuzzerutil::UpdateModuleIdBound(ir_context, bit_extract.result_id()); |
| bit_extract_ids.push_back( |
| {SPV_OPERAND_TYPE_ID, {bit_extract.result_id()}}); |
| } |
| |
| // Applies |bit_instruction| to the extracted bits. |
| auto extracted_bit_instruction = opt::Instruction( |
| ir_context, bit_instruction->opcode(), bit_instruction->type_id(), |
| *fresh_id++, bit_extract_ids); |
| bit_instruction->InsertBefore( |
| MakeUnique<opt::Instruction>(extracted_bit_instruction)); |
| fuzzerutil::UpdateModuleIdBound(ir_context, |
| extracted_bit_instruction.result_id()); |
| extracted_bit_instructions[i] = extracted_bit_instruction.result_id(); |
| } |
| |
| // The first two ids in |extracted_bit_instructions| are used to insert the |
| // first two bits of the result. |
| uint32_t offset = fuzzerutil::MaybeGetIntegerConstant( |
| ir_context, *transformation_context, {1}, 32, false, false); |
| auto bit_insert = opt::Instruction( |
| ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(), *fresh_id++, |
| {{SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[0]}}, |
| {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[1]}}, |
| {SPV_OPERAND_TYPE_ID, {offset}}, |
| {SPV_OPERAND_TYPE_ID, {count}}}); |
| bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert)); |
| fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id()); |
| |
| // Inserts the remaining bits. |
| for (uint32_t i = 2; i < width; i++) { |
| offset = fuzzerutil::MaybeGetIntegerConstant( |
| ir_context, *transformation_context, {i}, 32, false, false); |
| bit_insert = opt::Instruction( |
| ir_context, SpvOpBitFieldInsert, bit_instruction->type_id(), |
| *fresh_id++, |
| {{SPV_OPERAND_TYPE_ID, {bit_insert.result_id()}}, |
| {SPV_OPERAND_TYPE_ID, {extracted_bit_instructions[i]}}, |
| {SPV_OPERAND_TYPE_ID, {offset}}, |
| {SPV_OPERAND_TYPE_ID, {count}}}); |
| bit_instruction->InsertBefore(MakeUnique<opt::Instruction>(bit_insert)); |
| fuzzerutil::UpdateModuleIdBound(ir_context, bit_insert.result_id()); |
| } |
| |
| ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); |
| |
| // We only add a synonym fact if the bit instruction is not irrelevant, and if |
| // the new result id we would make it synonymous with is not irrelevant. (It |
| // could be irrelevant if we are in a dead block.) |
| if (!transformation_context->GetFactManager()->IdIsIrrelevant( |
| bit_instruction->result_id()) && |
| !transformation_context->GetFactManager()->IdIsIrrelevant( |
| bit_insert.result_id())) { |
| // Adds the fact that the last |bit_insert| instruction is synonymous of |
| // |bit_instruction|. |
| transformation_context->GetFactManager()->AddFactDataSynonym( |
| MakeDataDescriptor(bit_insert.result_id(), {}), |
| MakeDataDescriptor(bit_instruction->result_id(), {})); |
| } |
| } |
| |
| std::unordered_set<uint32_t> |
| TransformationAddBitInstructionSynonym::GetFreshIds() const { |
| std::unordered_set<uint32_t> result; |
| for (auto id : message_.fresh_ids()) { |
| result.insert(id); |
| } |
| return result; |
| } |
| |
| } // namespace fuzz |
| } // namespace spvtools |