| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- // 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_up.h"
- #include "source/fuzz/fuzzer_util.h"
- #include "source/fuzz/instruction_descriptor.h"
- namespace spvtools {
- namespace fuzz {
- namespace {
- uint32_t GetResultIdFromLabelId(const opt::Instruction& phi_inst,
- uint32_t label_id) {
- assert(phi_inst.opcode() == spv::Op::OpPhi && "|phi_inst| is not an OpPhi");
- for (uint32_t i = 1; i < phi_inst.NumInOperands(); i += 2) {
- if (phi_inst.GetSingleWordInOperand(i) == label_id) {
- return phi_inst.GetSingleWordInOperand(i - 1);
- }
- }
- return 0;
- }
- bool ContainsPointers(const opt::analysis::Type& type) {
- switch (type.kind()) {
- case opt::analysis::Type::kPointer:
- return true;
- case opt::analysis::Type::kStruct:
- return std::any_of(type.AsStruct()->element_types().begin(),
- type.AsStruct()->element_types().end(),
- [](const opt::analysis::Type* element_type) {
- return ContainsPointers(*element_type);
- });
- default:
- return false;
- }
- }
- bool HasValidDependencies(opt::IRContext* ir_context, opt::Instruction* inst) {
- const auto* inst_block = ir_context->get_instr_block(inst);
- assert(inst_block &&
- "This function shouldn't be applied to global instructions or function"
- "parameters");
- for (uint32_t i = 0; i < inst->NumInOperands(); ++i) {
- const auto& operand = inst->GetInOperand(i);
- if (operand.type != SPV_OPERAND_TYPE_ID) {
- // Consider only <id> operands.
- continue;
- }
- auto* dependency = ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
- assert(dependency && "Operand has invalid id");
- if (ir_context->get_instr_block(dependency) == inst_block &&
- dependency->opcode() != spv::Op::OpPhi) {
- // |dependency| is "valid" if it's an OpPhi from the same basic block or
- // an instruction from a different basic block.
- return false;
- }
- }
- return true;
- }
- } // namespace
- TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
- protobufs::TransformationPropagateInstructionUp message)
- : message_(std::move(message)) {}
- TransformationPropagateInstructionUp::TransformationPropagateInstructionUp(
- uint32_t block_id,
- const std::map<uint32_t, uint32_t>& predecessor_id_to_fresh_id) {
- message_.set_block_id(block_id);
- *message_.mutable_predecessor_id_to_fresh_id() =
- fuzzerutil::MapToRepeatedUInt32Pair(predecessor_id_to_fresh_id);
- }
- bool TransformationPropagateInstructionUp::IsApplicable(
- opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
- // Check that we can apply this transformation to the |block_id|.
- if (!IsApplicableToBlock(ir_context, message_.block_id())) {
- return false;
- }
- const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
- message_.predecessor_id_to_fresh_id());
- for (auto id : ir_context->cfg()->preds(message_.block_id())) {
- // Each predecessor must have a fresh id in the |predecessor_id_to_fresh_id|
- // map.
- if (!predecessor_id_to_fresh_id.count(id)) {
- return false;
- }
- }
- std::vector<uint32_t> maybe_fresh_ids;
- maybe_fresh_ids.reserve(predecessor_id_to_fresh_id.size());
- for (const auto& entry : predecessor_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 TransformationPropagateInstructionUp::Apply(
- opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
- auto* inst = GetInstructionToPropagate(ir_context, message_.block_id());
- assert(inst &&
- "The block must have at least one supported instruction to propagate");
- assert(inst->result_id() && inst->type_id() &&
- "|inst| must have a result id and a type id");
- opt::Instruction::OperandList op_phi_operands;
- const auto predecessor_id_to_fresh_id = fuzzerutil::RepeatedUInt32PairToMap(
- message_.predecessor_id_to_fresh_id());
- std::unordered_set<uint32_t> visited_predecessors;
- for (auto predecessor_id : ir_context->cfg()->preds(message_.block_id())) {
- // A block can have multiple identical predecessors.
- if (visited_predecessors.count(predecessor_id)) {
- continue;
- }
- visited_predecessors.insert(predecessor_id);
- auto new_result_id = predecessor_id_to_fresh_id.at(predecessor_id);
- // Compute InOperands for the OpPhi instruction to be inserted later.
- op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {new_result_id}});
- op_phi_operands.push_back({SPV_OPERAND_TYPE_ID, {predecessor_id}});
- // Create a clone of the |inst| to be inserted into the |predecessor_id|.
- std::unique_ptr<opt::Instruction> clone(inst->Clone(ir_context));
- clone->SetResultId(new_result_id);
- fuzzerutil::UpdateModuleIdBound(ir_context, new_result_id);
- // Adjust |clone|'s operands to account for possible dependencies on OpPhi
- // instructions from the same basic block.
- for (uint32_t i = 0; i < clone->NumInOperands(); ++i) {
- auto& operand = clone->GetInOperand(i);
- if (operand.type != SPV_OPERAND_TYPE_ID) {
- // Consider only ids.
- continue;
- }
- const auto* dependency_inst =
- ir_context->get_def_use_mgr()->GetDef(operand.words[0]);
- assert(dependency_inst && "|clone| depends on an invalid id");
- if (ir_context->get_instr_block(dependency_inst->result_id()) !=
- ir_context->cfg()->block(message_.block_id())) {
- // We don't need to adjust anything if |dependency_inst| is from a
- // different block, a global instruction or a function parameter.
- continue;
- }
- assert(dependency_inst->opcode() == spv::Op::OpPhi &&
- "Propagated instruction can depend only on OpPhis from the same "
- "basic block or instructions from different basic blocks");
- auto new_id = GetResultIdFromLabelId(*dependency_inst, predecessor_id);
- assert(new_id && "OpPhi instruction is missing a predecessor");
- operand.words[0] = new_id;
- }
- auto* insert_before_inst = fuzzerutil::GetLastInsertBeforeInstruction(
- ir_context, predecessor_id, clone->opcode());
- assert(insert_before_inst && "Can't insert |clone| into |predecessor_id");
- insert_before_inst->InsertBefore(std::move(clone));
- }
- // Insert an OpPhi instruction into the basic block of |inst|.
- ir_context->get_instr_block(inst)->begin()->InsertBefore(
- MakeUnique<opt::Instruction>(ir_context, spv::Op::OpPhi, inst->type_id(),
- inst->result_id(),
- std::move(op_phi_operands)));
- // Remove |inst| from the basic block.
- ir_context->KillInst(inst);
- // We have changed the module so most analyzes are now invalid.
- ir_context->InvalidateAnalysesExceptFor(
- opt::IRContext::Analysis::kAnalysisNone);
- }
- protobufs::Transformation TransformationPropagateInstructionUp::ToMessage()
- const {
- protobufs::Transformation result;
- *result.mutable_propagate_instruction_up() = message_;
- return result;
- }
- bool TransformationPropagateInstructionUp::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*
- TransformationPropagateInstructionUp::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& inst : *block) {
- // We look for the first instruction in the block that satisfies the
- // following rules:
- // - it's not an OpPhi
- // - it must be supported by this transformation
- // - it may depend only on instructions from different basic blocks or on
- // OpPhi instructions from the same basic block.
- if (inst.opcode() == spv::Op::OpPhi || !IsOpcodeSupported(inst.opcode()) ||
- !inst.type_id() || !inst.result_id()) {
- continue;
- }
- const auto* inst_type = ir_context->get_type_mgr()->GetType(inst.type_id());
- assert(inst_type && "|inst| has invalid type");
- if (inst_type->AsSampledImage()) {
- // OpTypeSampledImage cannot be used as an argument to OpPhi instructions,
- // thus we cannot support this type.
- continue;
- }
- if (!ir_context->get_feature_mgr()->HasCapability(
- spv::Capability::VariablePointersStorageBuffer) &&
- ContainsPointers(*inst_type)) {
- // OpPhi supports pointer operands only with VariablePointers or
- // VariablePointersStorageBuffer capabilities.
- //
- // Note that VariablePointers capability implicitly declares
- // VariablePointersStorageBuffer capability.
- continue;
- }
- if (!HasValidDependencies(ir_context, &inst)) {
- continue;
- }
- return &inst;
- }
- return nullptr;
- }
- bool TransformationPropagateInstructionUp::IsApplicableToBlock(
- opt::IRContext* ir_context, uint32_t block_id) {
- // Check that |block_id| is valid.
- const auto* label_inst = ir_context->get_def_use_mgr()->GetDef(block_id);
- if (!label_inst || label_inst->opcode() != spv::Op::OpLabel) {
- return false;
- }
- // Check that |block| has predecessors.
- const auto& predecessors = ir_context->cfg()->preds(block_id);
- if (predecessors.empty()) {
- return false;
- }
- // The block must contain an instruction to propagate.
- const auto* inst_to_propagate =
- GetInstructionToPropagate(ir_context, block_id);
- if (!inst_to_propagate) {
- return false;
- }
- // We should be able to insert |inst_to_propagate| into every predecessor of
- // |block|.
- return std::all_of(predecessors.begin(), predecessors.end(),
- [ir_context, inst_to_propagate](uint32_t predecessor_id) {
- return fuzzerutil::GetLastInsertBeforeInstruction(
- ir_context, predecessor_id,
- inst_to_propagate->opcode()) != nullptr;
- });
- }
- std::unordered_set<uint32_t> TransformationPropagateInstructionUp::GetFreshIds()
- const {
- std::unordered_set<uint32_t> result;
- for (auto& pair : message_.predecessor_id_to_fresh_id()) {
- result.insert(pair.second());
- }
- return result;
- }
- } // namespace fuzz
- } // namespace spvtools
|