2020-10-06 12:38:19 +00:00
|
|
|
// 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(
|
2021-03-23 13:31:44 +00:00
|
|
|
protobufs::TransformationPropagateInstructionDown message)
|
|
|
|
: message_(std::move(message)) {}
|
2020-10-06 12:38:19 +00:00
|
|
|
|
|
|
|
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, 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<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(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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// |block| must be reachable.
|
2021-06-28 19:00:14 +00:00
|
|
|
if (!ir_context->IsReachable(*block)) {
|
2020-10-06 12:38:19 +00:00
|
|
|
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);
|
|
|
|
|
2021-06-28 19:00:14 +00:00
|
|
|
const auto* dominator_analysis =
|
|
|
|
ir_context->GetDominatorAnalysis(block->GetParent());
|
|
|
|
|
2020-10-06 12:38:19 +00:00
|
|
|
// 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<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|.
|
2021-06-28 19:00:14 +00:00
|
|
|
if (!ir_context->IsReachable(*ir_context->cfg()->block(merge_block_id)) ||
|
2020-10-06 12:38:19 +00:00
|
|
|
!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<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
|