| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- // 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<uint32_t, uint32_t>& 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<uint32_t> 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<uint32_t> 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<opt::Instruction> 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<uint32_t> 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<opt::Instruction>(
- ir_context, spv::Op::OpPhi, 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<uint32_t> 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(spv::Op 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 spv::Op::OpUndef:
- case spv::Op::OpAccessChain:
- case spv::Op::OpInBoundsAccessChain:
- case spv::Op::OpArrayLength:
- case spv::Op::OpVectorExtractDynamic:
- case spv::Op::OpVectorInsertDynamic:
- case spv::Op::OpVectorShuffle:
- case spv::Op::OpCompositeConstruct:
- case spv::Op::OpCompositeExtract:
- case spv::Op::OpCompositeInsert:
- case spv::Op::OpCopyObject:
- case spv::Op::OpTranspose:
- case spv::Op::OpConvertFToU:
- case spv::Op::OpConvertFToS:
- case spv::Op::OpConvertSToF:
- case spv::Op::OpConvertUToF:
- case spv::Op::OpUConvert:
- case spv::Op::OpSConvert:
- case spv::Op::OpFConvert:
- case spv::Op::OpQuantizeToF16:
- case spv::Op::OpSatConvertSToU:
- case spv::Op::OpSatConvertUToS:
- case spv::Op::OpBitcast:
- case spv::Op::OpSNegate:
- case spv::Op::OpFNegate:
- case spv::Op::OpIAdd:
- case spv::Op::OpFAdd:
- case spv::Op::OpISub:
- case spv::Op::OpFSub:
- case spv::Op::OpIMul:
- case spv::Op::OpFMul:
- case spv::Op::OpUDiv:
- case spv::Op::OpSDiv:
- case spv::Op::OpFDiv:
- case spv::Op::OpUMod:
- case spv::Op::OpSRem:
- case spv::Op::OpSMod:
- case spv::Op::OpFRem:
- case spv::Op::OpFMod:
- case spv::Op::OpVectorTimesScalar:
- case spv::Op::OpMatrixTimesScalar:
- case spv::Op::OpVectorTimesMatrix:
- case spv::Op::OpMatrixTimesVector:
- case spv::Op::OpMatrixTimesMatrix:
- case spv::Op::OpOuterProduct:
- case spv::Op::OpDot:
- case spv::Op::OpIAddCarry:
- case spv::Op::OpISubBorrow:
- case spv::Op::OpUMulExtended:
- case spv::Op::OpSMulExtended:
- case spv::Op::OpAny:
- case spv::Op::OpAll:
- case spv::Op::OpIsNan:
- case spv::Op::OpIsInf:
- case spv::Op::OpIsFinite:
- case spv::Op::OpIsNormal:
- case spv::Op::OpSignBitSet:
- case spv::Op::OpLessOrGreater:
- case spv::Op::OpOrdered:
- case spv::Op::OpUnordered:
- case spv::Op::OpLogicalEqual:
- case spv::Op::OpLogicalNotEqual:
- case spv::Op::OpLogicalOr:
- case spv::Op::OpLogicalAnd:
- case spv::Op::OpLogicalNot:
- case spv::Op::OpSelect:
- case spv::Op::OpIEqual:
- case spv::Op::OpINotEqual:
- case spv::Op::OpUGreaterThan:
- case spv::Op::OpSGreaterThan:
- case spv::Op::OpUGreaterThanEqual:
- case spv::Op::OpSGreaterThanEqual:
- case spv::Op::OpULessThan:
- case spv::Op::OpSLessThan:
- case spv::Op::OpULessThanEqual:
- case spv::Op::OpSLessThanEqual:
- case spv::Op::OpFOrdEqual:
- case spv::Op::OpFUnordEqual:
- case spv::Op::OpFOrdNotEqual:
- case spv::Op::OpFUnordNotEqual:
- case spv::Op::OpFOrdLessThan:
- case spv::Op::OpFUnordLessThan:
- case spv::Op::OpFOrdGreaterThan:
- case spv::Op::OpFUnordGreaterThan:
- case spv::Op::OpFOrdLessThanEqual:
- case spv::Op::OpFUnordLessThanEqual:
- case spv::Op::OpFOrdGreaterThanEqual:
- case spv::Op::OpFUnordGreaterThanEqual:
- case spv::Op::OpShiftRightLogical:
- case spv::Op::OpShiftRightArithmetic:
- case spv::Op::OpShiftLeftLogical:
- case spv::Op::OpBitwiseOr:
- case spv::Op::OpBitwiseXor:
- case spv::Op::OpBitwiseAnd:
- case spv::Op::OpNot:
- case spv::Op::OpBitFieldInsert:
- case spv::Op::OpBitFieldSExtract:
- case spv::Op::OpBitFieldUExtract:
- case spv::Op::OpBitReverse:
- case spv::Op::OpBitCount:
- case spv::Op::OpCopyLogical:
- case spv::Op::OpPtrEqual:
- case spv::Op::OpPtrNotEqual:
- 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;
- }
- // |block| must be reachable.
- if (!ir_context->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() != spv::Op::OpPhi) {
- // 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);
- const auto* dominator_analysis =
- ir_context->GetDominatorAnalysis(block->GetParent());
- // 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() == spv::Op::OpPhi
- ? 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, spv::Op 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<uint32_t>
- 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<uint32_t> 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<uint32_t>& 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 (!ir_context->IsReachable(*ir_context->cfg()->block(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(
- spv::Capability::VariablePointersStorageBuffer)) {
- return 0;
- }
- return merge_block_id;
- }
- std::unordered_set<uint32_t>
- TransformationPropagateInstructionDown::GetFreshIds() const {
- std::unordered_set<uint32_t> 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
|