// Copyright (c) 2020 Vasyl Teliman // // 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_propagate_instruction_down.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_descriptor.h" namespace spvtools { namespace fuzz { TransformationPropagateInstructionDown::TransformationPropagateInstructionDown( protobufs::TransformationPropagateInstructionDown message) : message_(std::move(message)) {} TransformationPropagateInstructionDown::TransformationPropagateInstructionDown( uint32_t block_id, uint32_t phi_fresh_id, const std::map& successor_id_to_fresh_id) { message_.set_block_id(block_id); message_.set_phi_fresh_id(phi_fresh_id); *message_.mutable_successor_id_to_fresh_id() = fuzzerutil::MapToRepeatedUInt32Pair(successor_id_to_fresh_id); } bool TransformationPropagateInstructionDown::IsApplicable( opt::IRContext* ir_context, const TransformationContext& transformation_context) const { // Check that we can apply this transformation to the |block_id|. if (!IsApplicableToBlock(ir_context, message_.block_id())) { return false; } const auto successor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id()); for (auto id : GetAcceptableSuccessors(ir_context, message_.block_id())) { // Each successor must have a fresh id in the |successor_id_to_fresh_id| // map, unless overflow ids are available. if (!successor_id_to_fresh_id.count(id) && !transformation_context.GetOverflowIdSource()->HasOverflowIds()) { return false; } } std::vector maybe_fresh_ids = {message_.phi_fresh_id()}; maybe_fresh_ids.reserve(successor_id_to_fresh_id.size()); for (const auto& entry : successor_id_to_fresh_id) { maybe_fresh_ids.push_back(entry.second); } // All ids must be unique and fresh. return !fuzzerutil::HasDuplicates(maybe_fresh_ids) && std::all_of(maybe_fresh_ids.begin(), maybe_fresh_ids.end(), [ir_context](uint32_t id) { return fuzzerutil::IsFreshId(ir_context, id); }); } void TransformationPropagateInstructionDown::Apply( opt::IRContext* ir_context, TransformationContext* transformation_context) const { // Get instruction to propagate down. There must be one. auto* inst_to_propagate = GetInstructionToPropagate(ir_context, message_.block_id()); assert(inst_to_propagate && "There must be an instruction to propagate"); auto successor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(message_.successor_id_to_fresh_id()); std::vector created_inst_ids; auto successor_ids = GetAcceptableSuccessors(ir_context, message_.block_id()); // Clone |inst_to_propagate| into every successor. for (auto successor_id : successor_ids) { std::unique_ptr clone( inst_to_propagate->Clone(ir_context)); uint32_t new_result_id; if (successor_id_to_fresh_id.count(successor_id)) { new_result_id = successor_id_to_fresh_id.at(successor_id); } else { assert(transformation_context->GetOverflowIdSource()->HasOverflowIds() && "Overflow ids must be available"); new_result_id = transformation_context->GetOverflowIdSource()->GetNextOverflowId(); successor_id_to_fresh_id[successor_id] = new_result_id; } clone->SetResultId(new_result_id); fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id); auto* insert_before_inst = GetFirstInsertBeforeInstruction( ir_context, successor_id, clone->opcode()); assert(insert_before_inst && "Can't insert into one of the successors"); insert_before_inst->InsertBefore(std::move(clone)); created_inst_ids.push_back(new_result_id); } // Add an OpPhi instruction into the module if possible. if (auto merge_block_id = GetOpPhiBlockId( ir_context, message_.block_id(), *inst_to_propagate, successor_ids)) { opt::Instruction::OperandList in_operands; std::unordered_set visited_predecessors; for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) { if (visited_predecessors.count(predecessor_id)) { // Merge block might have multiple identical predecessors. continue; } visited_predecessors.insert(predecessor_id); const auto* dominator_analysis = ir_context->GetDominatorAnalysis( ir_context->cfg()->block(message_.block_id())->GetParent()); // Find the successor of |source_block| that dominates the predecessor of // the merge block |predecessor_id|. auto it = std::find_if( successor_ids.begin(), successor_ids.end(), [predecessor_id, dominator_analysis](uint32_t successor_id) { return dominator_analysis->Dominates(successor_id, predecessor_id); }); // OpPhi requires a single operand pair for every predecessor of the // OpPhi's block. assert(it != successor_ids.end() && "Unable to insert OpPhi"); in_operands.push_back( {SPV_OPERAND_TYPE_ID, {successor_id_to_fresh_id.at(*it)}}); in_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}}); } ir_context->cfg() ->block(merge_block_id) ->begin() ->InsertBefore(MakeUnique( ir_context, SpvOpPhi, inst_to_propagate->type_id(), message_.phi_fresh_id(), std::move(in_operands))); fuzzerutil::UpdateModuleIdBound(ir_context, message_.phi_fresh_id()); created_inst_ids.push_back(message_.phi_fresh_id()); } // Make sure analyses are updated when we adjust users of |inst_to_propagate|. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); // Copy decorations from the original instructions to its propagated copies. for (auto id : created_inst_ids) { ir_context->get_decoration_mgr()->CloneDecorations( inst_to_propagate->result_id(), id); } // Remove all decorations from the original instruction. ir_context->get_decoration_mgr()->RemoveDecorationsFrom( inst_to_propagate->result_id()); // Update every use of the |inst_to_propagate| with a result id of some of the // newly created instructions. ir_context->get_def_use_mgr()->ForEachUse( inst_to_propagate, [ir_context, &created_inst_ids]( opt::Instruction* user, uint32_t operand_index) { assert(ir_context->get_instr_block(user) && "All decorations should have already been adjusted"); auto in_operand_index = fuzzerutil::InOperandIndexFromOperandIndex(*user, operand_index); for (auto id : created_inst_ids) { if (fuzzerutil::IdIsAvailableAtUse(ir_context, user, in_operand_index, id)) { user->SetInOperand(in_operand_index, {id}); return; } } // Every user of |inst_to_propagate| must be updated since we will // remove that instruction from the module. assert(false && "Every user of |inst_to_propagate| must be updated"); }); // Add synonyms about newly created instructions. assert(inst_to_propagate->HasResultId() && "Result id is required to add facts"); if (transformation_context->GetFactManager()->IdIsIrrelevant( inst_to_propagate->result_id())) { for (auto id : created_inst_ids) { transformation_context->GetFactManager()->AddFactIdIsIrrelevant(id); } } else { std::vector non_irrelevant_ids; for (auto id : created_inst_ids) { // |id| can be irrelevant implicitly (e.g. if we propagate it into a dead // block). if (!transformation_context->GetFactManager()->IdIsIrrelevant(id)) { non_irrelevant_ids.push_back(id); } } if (transformation_context->GetFactManager()->PointeeValueIsIrrelevant( inst_to_propagate->result_id())) { for (auto id : non_irrelevant_ids) { transformation_context->GetFactManager() ->AddFactValueOfPointeeIsIrrelevant(id); } } for (auto id : non_irrelevant_ids) { transformation_context->GetFactManager()->AddFactDataSynonym( MakeDataDescriptor(id, {}), MakeDataDescriptor(non_irrelevant_ids[0], {})); } } // Remove the propagated instruction from the module. ir_context->KillInst(inst_to_propagate); // We've adjusted all users - make sure these changes are analyzed. ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone); } protobufs::Transformation TransformationPropagateInstructionDown::ToMessage() const { protobufs::Transformation result; *result.mutable_propagate_instruction_down() = message_; return result; } bool TransformationPropagateInstructionDown::IsOpcodeSupported(SpvOp opcode) { // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): // We only support "simple" instructions that don't work with memory. // We should extend this so that we support the ones that modify the memory // too. switch (opcode) { case SpvOpUndef: case SpvOpAccessChain: case SpvOpInBoundsAccessChain: case SpvOpArrayLength: case SpvOpVectorExtractDynamic: case SpvOpVectorInsertDynamic: case SpvOpVectorShuffle: case SpvOpCompositeConstruct: case SpvOpCompositeExtract: case SpvOpCompositeInsert: case SpvOpCopyObject: case SpvOpTranspose: case SpvOpConvertFToU: case SpvOpConvertFToS: case SpvOpConvertSToF: case SpvOpConvertUToF: case SpvOpUConvert: case SpvOpSConvert: case SpvOpFConvert: case SpvOpQuantizeToF16: case SpvOpSatConvertSToU: case SpvOpSatConvertUToS: case SpvOpBitcast: case SpvOpSNegate: case SpvOpFNegate: case SpvOpIAdd: case SpvOpFAdd: case SpvOpISub: case SpvOpFSub: case SpvOpIMul: case SpvOpFMul: case SpvOpUDiv: case SpvOpSDiv: case SpvOpFDiv: case SpvOpUMod: case SpvOpSRem: case SpvOpSMod: case SpvOpFRem: case SpvOpFMod: case SpvOpVectorTimesScalar: case SpvOpMatrixTimesScalar: case SpvOpVectorTimesMatrix: case SpvOpMatrixTimesVector: case SpvOpMatrixTimesMatrix: case SpvOpOuterProduct: case SpvOpDot: case SpvOpIAddCarry: case SpvOpISubBorrow: case SpvOpUMulExtended: case SpvOpSMulExtended: case SpvOpAny: case SpvOpAll: case SpvOpIsNan: case SpvOpIsInf: case SpvOpIsFinite: case SpvOpIsNormal: case SpvOpSignBitSet: case SpvOpLessOrGreater: case SpvOpOrdered: case SpvOpUnordered: case SpvOpLogicalEqual: case SpvOpLogicalNotEqual: case SpvOpLogicalOr: case SpvOpLogicalAnd: case SpvOpLogicalNot: case SpvOpSelect: case SpvOpIEqual: case SpvOpINotEqual: case SpvOpUGreaterThan: case SpvOpSGreaterThan: case SpvOpUGreaterThanEqual: case SpvOpSGreaterThanEqual: case SpvOpULessThan: case SpvOpSLessThan: case SpvOpULessThanEqual: case SpvOpSLessThanEqual: case SpvOpFOrdEqual: case SpvOpFUnordEqual: case SpvOpFOrdNotEqual: case SpvOpFUnordNotEqual: case SpvOpFOrdLessThan: case SpvOpFUnordLessThan: case SpvOpFOrdGreaterThan: case SpvOpFUnordGreaterThan: case SpvOpFOrdLessThanEqual: case SpvOpFUnordLessThanEqual: case SpvOpFOrdGreaterThanEqual: case SpvOpFUnordGreaterThanEqual: case SpvOpShiftRightLogical: case SpvOpShiftRightArithmetic: case SpvOpShiftLeftLogical: case SpvOpBitwiseOr: case SpvOpBitwiseXor: case SpvOpBitwiseAnd: case SpvOpNot: case SpvOpBitFieldInsert: case SpvOpBitFieldSExtract: case SpvOpBitFieldUExtract: case SpvOpBitReverse: case SpvOpBitCount: case SpvOpCopyLogical: case SpvOpPtrEqual: case SpvOpPtrNotEqual: return true; default: return false; } } opt::Instruction* TransformationPropagateInstructionDown::GetInstructionToPropagate( opt::IRContext* ir_context, uint32_t block_id) { auto* block = ir_context->cfg()->block(block_id); assert(block && "|block_id| is invalid"); for (auto it = block->rbegin(); it != block->rend(); ++it) { if (!it->result_id() || !it->type_id() || !IsOpcodeSupported(it->opcode())) { continue; } auto all_users_from_different_blocks = ir_context->get_def_use_mgr()->WhileEachUser( &*it, [ir_context, block](opt::Instruction* user) { return ir_context->get_instr_block(user) != block; }); if (!all_users_from_different_blocks) { // We can't propagate an instruction if it's used in the same block. continue; } return &*it; } return nullptr; } bool TransformationPropagateInstructionDown::IsApplicableToBlock( opt::IRContext* ir_context, uint32_t block_id) { // Check that |block_id| is valid. const auto* block = fuzzerutil::MaybeFindBlock(ir_context, block_id); if (!block) { return false; } const auto* dominator_analysis = ir_context->GetDominatorAnalysis(block->GetParent()); // |block| must be reachable. if (!dominator_analysis->IsReachable(block)) { return false; } // The block must have an instruction to propagate. const auto* inst_to_propagate = GetInstructionToPropagate(ir_context, block_id); if (!inst_to_propagate) { return false; } // Check that |block| has successors. auto successor_ids = GetAcceptableSuccessors(ir_context, block_id); if (successor_ids.empty()) { return false; } // Check that |successor_block| doesn't have any OpPhi instructions that // use |inst|. for (auto successor_id : successor_ids) { for (const auto& maybe_phi_inst : *ir_context->cfg()->block(successor_id)) { if (maybe_phi_inst.opcode() != SpvOpPhi) { // OpPhis can be intermixed with OpLine and OpNoLine. continue; } for (uint32_t i = 0; i < maybe_phi_inst.NumInOperands(); i += 2) { if (maybe_phi_inst.GetSingleWordInOperand(i) == inst_to_propagate->result_id()) { return false; } } } } // Get the result id of the block we will insert OpPhi instruction into. // This is either 0 or a result id of some merge block in the function. auto phi_block_id = GetOpPhiBlockId(ir_context, block_id, *inst_to_propagate, successor_ids); // Make sure we can adjust all users of the propagated instruction. return ir_context->get_def_use_mgr()->WhileEachUse( inst_to_propagate, [ir_context, &successor_ids, dominator_analysis, phi_block_id]( opt::Instruction* user, uint32_t index) { const auto* user_block = ir_context->get_instr_block(user); if (!user_block) { // |user| might be a global instruction (e.g. OpDecorate). return true; } // Check that at least one of the ids in |successor_ids| or a // |phi_block_id| dominates |user|'s block (or its predecessor if the // user is an OpPhi). We can't use fuzzerutil::IdIsAvailableAtUse since // the id in question hasn't yet been created in the module. auto block_id_to_dominate = user->opcode() == SpvOpPhi ? user->GetSingleWordOperand(index + 1) : user_block->id(); if (phi_block_id != 0 && dominator_analysis->Dominates(phi_block_id, block_id_to_dominate)) { return true; } return std::any_of( successor_ids.begin(), successor_ids.end(), [dominator_analysis, block_id_to_dominate](uint32_t id) { return dominator_analysis->Dominates(id, block_id_to_dominate); }); }); } opt::Instruction* TransformationPropagateInstructionDown::GetFirstInsertBeforeInstruction( opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode) { auto* block = ir_context->cfg()->block(block_id); auto it = block->begin(); while (it != block->end() && !fuzzerutil::CanInsertOpcodeBeforeInstruction(opcode, it)) { ++it; } return it == block->end() ? nullptr : &*it; } std::unordered_set TransformationPropagateInstructionDown::GetAcceptableSuccessors( opt::IRContext* ir_context, uint32_t block_id) { const auto* block = ir_context->cfg()->block(block_id); assert(block && "|block_id| is invalid"); const auto* inst = GetInstructionToPropagate(ir_context, block_id); assert(inst && "The block must have an instruction to propagate"); std::unordered_set result; block->ForEachSuccessorLabel([ir_context, &result, inst](uint32_t successor_id) { if (result.count(successor_id)) { return; } auto* successor_block = ir_context->cfg()->block(successor_id); // We can't propagate |inst| into |successor_block| if the latter is not // dominated by the |inst|'s dependencies. if (!inst->WhileEachInId([ir_context, successor_block](const uint32_t* id) { return fuzzerutil::IdIsAvailableBeforeInstruction( ir_context, &*successor_block->begin(), *id); })) { return; } // We don't propagate any "special" instructions (e.g. OpSelectionMerge // etc), thus, insertion point must always exist if the module is valid. assert(GetFirstInsertBeforeInstruction(ir_context, successor_id, inst->opcode()) && "There must exist an insertion point."); result.insert(successor_id); }); return result; } uint32_t TransformationPropagateInstructionDown::GetOpPhiBlockId( opt::IRContext* ir_context, uint32_t block_id, const opt::Instruction& inst_to_propagate, const std::unordered_set& successor_ids) { const auto* block = ir_context->cfg()->block(block_id); // |block_id| must belong to some construct. auto merge_block_id = block->GetMergeInst() ? block->GetMergeInst()->GetSingleWordInOperand(0) : ir_context->GetStructuredCFGAnalysis()->MergeBlock(block_id); if (!merge_block_id) { return 0; } const auto* dominator_analysis = ir_context->GetDominatorAnalysis(block->GetParent()); // Check that |merge_block_id| is reachable in the CFG and |block_id| // dominates |merge_block_id|. if (!dominator_analysis->IsReachable(merge_block_id) || !dominator_analysis->Dominates(block_id, merge_block_id)) { return 0; } // We can't insert an OpPhi into |merge_block_id| if it's an acceptable // successor of |block_id|. if (successor_ids.count(merge_block_id)) { return 0; } // All predecessors of the merge block must be dominated by at least one // successor of the |block_id|. assert(!ir_context->cfg()->preds(merge_block_id).empty() && "Merge block must be reachable"); for (auto predecessor_id : ir_context->cfg()->preds(merge_block_id)) { if (std::none_of( successor_ids.begin(), successor_ids.end(), [dominator_analysis, predecessor_id](uint32_t successor_id) { return dominator_analysis->Dominates(successor_id, predecessor_id); })) { return 0; } } const auto* propagate_type = ir_context->get_type_mgr()->GetType(inst_to_propagate.type_id()); assert(propagate_type && "|inst_to_propagate| must have a valid type"); // VariablePointers capability implicitly declares // VariablePointersStorageBuffer. We need those capabilities since otherwise // OpPhi instructions cannot have operands of pointer types. if (propagate_type->AsPointer() && !ir_context->get_feature_mgr()->HasCapability( SpvCapabilityVariablePointersStorageBuffer)) { return 0; } return merge_block_id; } std::unordered_set TransformationPropagateInstructionDown::GetFreshIds() const { std::unordered_set result = {message_.phi_fresh_id()}; for (const auto& pair : message_.successor_id_to_fresh_id()) { result.insert(pair.second()); } return result; } } // namespace fuzz } // namespace spvtools