SPIRV-Tools/source/fuzz/transformation_duplicate_region_with_selection.cpp
Alastair Donaldson 183fb9fe4c
spirv-fuzz: Fix problem with instruction context (#4394)
Adds an additional validity check to ensure that every instruction's
context pointer matches the enclosing IR context. Avoids a redundant
copy constructor call in TransformationDuplicateRegionWithSelection
that was leading to a bad IR context for some instructions.

Related: #4387, #4388.
Fixes #4393.
2021-07-22 23:44:29 +01:00

722 lines
29 KiB
C++

// Copyright (c) 2020 Google LLC
//
// 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_duplicate_region_with_selection.h"
#include "source/fuzz/fuzzer_util.h"
namespace spvtools {
namespace fuzz {
TransformationDuplicateRegionWithSelection::
TransformationDuplicateRegionWithSelection(
protobufs::TransformationDuplicateRegionWithSelection message)
: message_(std::move(message)) {}
TransformationDuplicateRegionWithSelection::
TransformationDuplicateRegionWithSelection(
uint32_t new_entry_fresh_id, uint32_t condition_id,
uint32_t merge_label_fresh_id, uint32_t entry_block_id,
uint32_t exit_block_id,
const std::map<uint32_t, uint32_t>& original_label_to_duplicate_label,
const std::map<uint32_t, uint32_t>& original_id_to_duplicate_id,
const std::map<uint32_t, uint32_t>& original_id_to_phi_id) {
message_.set_new_entry_fresh_id(new_entry_fresh_id);
message_.set_condition_id(condition_id);
message_.set_merge_label_fresh_id(merge_label_fresh_id);
message_.set_entry_block_id(entry_block_id);
message_.set_exit_block_id(exit_block_id);
*message_.mutable_original_label_to_duplicate_label() =
fuzzerutil::MapToRepeatedUInt32Pair(original_label_to_duplicate_label);
*message_.mutable_original_id_to_duplicate_id() =
fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_duplicate_id);
*message_.mutable_original_id_to_phi_id() =
fuzzerutil::MapToRepeatedUInt32Pair(original_id_to_phi_id);
}
bool TransformationDuplicateRegionWithSelection::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
// Instruction with the id |condition_id| must exist and must be of a bool
// type.
auto bool_instr =
ir_context->get_def_use_mgr()->GetDef(message_.condition_id());
if (bool_instr == nullptr || !bool_instr->type_id()) {
return false;
}
if (!ir_context->get_type_mgr()->GetType(bool_instr->type_id())->AsBool()) {
return false;
}
// The |new_entry_fresh_id| must be fresh and distinct.
std::set<uint32_t> ids_used_by_this_transformation;
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
message_.new_entry_fresh_id(), ir_context,
&ids_used_by_this_transformation)) {
return false;
}
// The |merge_label_fresh_id| must be fresh and distinct.
if (!CheckIdIsFreshAndNotUsedByThisTransformation(
message_.merge_label_fresh_id(), ir_context,
&ids_used_by_this_transformation)) {
return false;
}
// The entry and exit block ids must refer to blocks.
for (auto block_id : {message_.entry_block_id(), message_.exit_block_id()}) {
auto block_label = ir_context->get_def_use_mgr()->GetDef(block_id);
if (!block_label || block_label->opcode() != SpvOpLabel) {
return false;
}
}
auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
// The |entry_block| and the |exit_block| must be in the same function.
if (entry_block->GetParent() != exit_block->GetParent()) {
return false;
}
// The |entry_block| must dominate the |exit_block|.
auto dominator_analysis =
ir_context->GetDominatorAnalysis(entry_block->GetParent());
if (!dominator_analysis->Dominates(entry_block, exit_block)) {
return false;
}
// The |exit_block| must post-dominate the |entry_block|.
auto postdominator_analysis =
ir_context->GetPostDominatorAnalysis(entry_block->GetParent());
if (!postdominator_analysis->Dominates(exit_block, entry_block)) {
return false;
}
auto enclosing_function = entry_block->GetParent();
// |entry_block| cannot be the first block of the |enclosing_function|.
if (&*enclosing_function->begin() == entry_block) {
return false;
}
// To make the process of resolving OpPhi instructions easier, we require that
// the entry block has only one predecessor.
auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
std::sort(entry_block_preds.begin(), entry_block_preds.end());
entry_block_preds.erase(
std::unique(entry_block_preds.begin(), entry_block_preds.end()),
entry_block_preds.end());
if (entry_block_preds.size() > 1) {
return false;
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
// The following code has been copied from TransformationOutlineFunction.
// Consider refactoring to avoid duplication.
auto region_set = GetRegionBlocks(ir_context, entry_block, exit_block);
// Check whether |region_set| really is a single-entry single-exit region, and
// also check whether structured control flow constructs and their merge
// and continue constructs are either wholly in or wholly out of the region -
// e.g. avoid the situation where the region contains the head of a loop but
// not the loop's continue construct.
//
// This is achieved by going through every block in the |enclosing_function|
for (auto& block : *enclosing_function) {
if (&block == exit_block) {
// It is not OK for the exit block to head a loop construct or a
// conditional construct.
if (block.GetMergeInst()) {
return false;
}
continue;
}
if (region_set.count(&block) != 0) {
// The block is in the region and is not the region's exit block. Let's
// see whether all of the block's successors are in the region. If they
// are not, the region is not single-entry single-exit.
bool all_successors_in_region = true;
block.WhileEachSuccessorLabel([&all_successors_in_region, ir_context,
&region_set](uint32_t successor) -> bool {
if (region_set.count(ir_context->cfg()->block(successor)) == 0) {
all_successors_in_region = false;
return false;
}
return true;
});
if (!all_successors_in_region) {
return false;
}
}
if (auto merge = block.GetMergeInst()) {
// The block is a loop or selection header. The header and its
// associated merge block must be both in the region or both be
// outside the region.
auto merge_block =
ir_context->cfg()->block(merge->GetSingleWordOperand(0));
if (region_set.count(&block) != region_set.count(merge_block)) {
return false;
}
}
if (auto loop_merge = block.GetLoopMergeInst()) {
// The continue target of a loop must be within the region if and only if
// the header of the loop is.
auto continue_target =
ir_context->cfg()->block(loop_merge->GetSingleWordOperand(1));
// The continue target is a single-entry, single-exit region. Therefore,
// if the continue target is the exit block, the region might not contain
// the loop header. However, we would like to exclude this situation,
// since it would be impossible for the modified exit block to branch to
// the new selection merge block. In this scenario the exit block is
// required to branch to the loop header.
if (region_set.count(&block) != region_set.count(continue_target)) {
return false;
}
}
}
// Get the maps from the protobuf.
std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
fuzzerutil::RepeatedUInt32PairToMap(
message_.original_label_to_duplicate_label());
std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
fuzzerutil::RepeatedUInt32PairToMap(
message_.original_id_to_duplicate_id());
std::map<uint32_t, uint32_t> original_id_to_phi_id =
fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
for (auto block : region_set) {
// The label of every block in the region must be present in the map
// |original_label_to_duplicate_label|, unless overflow ids are present.
if (original_label_to_duplicate_label.count(block->id()) == 0) {
if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
return false;
}
} else {
auto duplicate_label = original_label_to_duplicate_label.at(block->id());
// Each id assigned to labels in the region must be distinct and fresh.
if (!duplicate_label ||
!CheckIdIsFreshAndNotUsedByThisTransformation(
duplicate_label, ir_context, &ids_used_by_this_transformation)) {
return false;
}
}
for (auto& instr : *block) {
if (!instr.HasResultId()) {
continue;
}
// Every instruction with a result id in the region must be present in the
// map |original_id_to_duplicate_id|, unless overflow ids are present.
if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
if (!transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
return false;
}
} else {
auto duplicate_id = original_id_to_duplicate_id.at(instr.result_id());
// Id assigned to this result id in the region must be distinct and
// fresh.
if (!duplicate_id ||
!CheckIdIsFreshAndNotUsedByThisTransformation(
duplicate_id, ir_context, &ids_used_by_this_transformation)) {
return false;
}
}
// If the instruction is available at the end of the region then we would
// like to be able to add an OpPhi instruction at the merge point of the
// duplicated region to capture the values computed by both duplicates of
// the instruction, so that this is also available after the region. We
// do this not just for instructions that are already used after the
// region, but for all instructions so that the phi is available to future
// transformations.
if (AvailableAfterRegion(instr, exit_block, ir_context)) {
if (!ValidOpPhiArgument(instr, ir_context)) {
// The instruction cannot be used as an OpPhi argument. This is a
// blocker if there are uses of the instruction after the region.
// Otherwise we can simply avoid generating an OpPhi for this
// instruction and its duplicate.
if (!ir_context->get_def_use_mgr()->WhileEachUser(
&instr,
[ir_context,
&region_set](opt::Instruction* use_instr) -> bool {
opt::BasicBlock* use_block =
ir_context->get_instr_block(use_instr);
return use_block == nullptr ||
region_set.count(use_block) > 0;
})) {
return false;
}
} else {
// Every instruction with a result id available at the end of the
// region must be present in the map |original_id_to_phi_id|, unless
// overflow ids are present.
if (original_id_to_phi_id.count(instr.result_id()) == 0) {
if (!transformation_context.GetOverflowIdSource()
->HasOverflowIds()) {
return false;
}
} else {
auto phi_id = original_id_to_phi_id.at(instr.result_id());
// Id assigned to this result id in the region must be distinct and
// fresh.
if (!phi_id ||
!CheckIdIsFreshAndNotUsedByThisTransformation(
phi_id, ir_context, &ids_used_by_this_transformation)) {
return false;
}
}
}
}
}
}
return true;
}
void TransformationDuplicateRegionWithSelection::Apply(
opt::IRContext* ir_context,
TransformationContext* transformation_context) const {
fuzzerutil::UpdateModuleIdBound(ir_context, message_.new_entry_fresh_id());
fuzzerutil::UpdateModuleIdBound(ir_context, message_.merge_label_fresh_id());
// Create the new entry block containing the main conditional instruction. Set
// its parent to the parent of the original entry block, since it is located
// in the same function.
std::unique_ptr<opt::BasicBlock> new_entry_block =
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
ir_context, SpvOpLabel, 0, message_.new_entry_fresh_id(),
opt::Instruction::OperandList()));
auto entry_block = ir_context->cfg()->block(message_.entry_block_id());
auto enclosing_function = entry_block->GetParent();
auto exit_block = ir_context->cfg()->block(message_.exit_block_id());
// Get the blocks contained in the region.
std::set<opt::BasicBlock*> region_blocks =
GetRegionBlocks(ir_context, entry_block, exit_block);
// Construct the merge block.
std::unique_ptr<opt::BasicBlock> merge_block =
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(),
opt::Instruction::OperandList()));
// Get the maps from the protobuf.
std::map<uint32_t, uint32_t> original_label_to_duplicate_label =
fuzzerutil::RepeatedUInt32PairToMap(
message_.original_label_to_duplicate_label());
std::map<uint32_t, uint32_t> original_id_to_duplicate_id =
fuzzerutil::RepeatedUInt32PairToMap(
message_.original_id_to_duplicate_id());
std::map<uint32_t, uint32_t> original_id_to_phi_id =
fuzzerutil::RepeatedUInt32PairToMap(message_.original_id_to_phi_id());
// Use oveflow ids to fill in any required ids that are missing from these
// maps.
for (auto block : region_blocks) {
if (original_label_to_duplicate_label.count(block->id()) == 0) {
original_label_to_duplicate_label.insert(
{block->id(),
transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
}
for (auto& instr : *block) {
if (!instr.HasResultId()) {
continue;
}
if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
original_id_to_duplicate_id.insert(
{instr.result_id(), transformation_context->GetOverflowIdSource()
->GetNextOverflowId()});
}
if (AvailableAfterRegion(instr, exit_block, ir_context) &&
ValidOpPhiArgument(instr, ir_context)) {
if (original_id_to_phi_id.count(instr.result_id()) == 0) {
original_id_to_phi_id.insert(
{instr.result_id(), transformation_context->GetOverflowIdSource()
->GetNextOverflowId()});
}
}
}
}
// Before adding duplicate blocks, we need to update the OpPhi instructions in
// the successors of the |exit_block|. We know that the execution of the
// transformed region will end in |merge_block|. Hence, we need to change all
// occurrences of the label id of the |exit_block| to the label id of the
// |merge_block|.
exit_block->ForEachSuccessorLabel([this, ir_context](uint32_t label_id) {
auto block = ir_context->cfg()->block(label_id);
for (auto& instr : *block) {
if (instr.opcode() == SpvOpPhi) {
instr.ForEachId([this](uint32_t* id) {
if (*id == message_.exit_block_id()) {
*id = message_.merge_label_fresh_id();
}
});
}
}
});
// Get vector of predecessors id of |entry_block|. Remove any duplicate
// values.
auto entry_block_preds = ir_context->cfg()->preds(entry_block->id());
std::sort(entry_block_preds.begin(), entry_block_preds.end());
entry_block_preds.erase(
unique(entry_block_preds.begin(), entry_block_preds.end()),
entry_block_preds.end());
// We know that |entry_block| has only one predecessor, since the region is
// single-entry, single-exit and its constructs and their merge blocks must be
// either wholly within or wholly outside of the region.
assert(entry_block_preds.size() == 1 &&
"The entry of the region to be duplicated can have only one "
"predecessor.");
uint32_t entry_block_pred_id =
ir_context->get_instr_block(entry_block_preds[0])->id();
// Update all the OpPhi instructions in the |entry_block|. Change every
// occurrence of |entry_block_pred_id| to the id of |new_entry|, because we
// will insert |new_entry| before |entry_block|.
for (auto& instr : *entry_block) {
if (instr.opcode() == SpvOpPhi) {
instr.ForEachId([this, entry_block_pred_id](uint32_t* id) {
if (*id == entry_block_pred_id) {
*id = message_.new_entry_fresh_id();
}
});
}
}
// Duplication of blocks will invalidate iterators. Store all the blocks from
// the enclosing function.
std::vector<opt::BasicBlock*> blocks;
for (auto& block : *enclosing_function) {
blocks.push_back(&block);
}
opt::BasicBlock* previous_block = nullptr;
opt::BasicBlock* duplicated_exit_block = nullptr;
// Iterate over all blocks of the function to duplicate blocks of the original
// region and their instructions.
for (auto& block : blocks) {
// The block must be contained in the region.
if (region_blocks.count(block) == 0) {
continue;
}
fuzzerutil::UpdateModuleIdBound(
ir_context, original_label_to_duplicate_label.at(block->id()));
std::unique_ptr<opt::BasicBlock> duplicated_block =
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
ir_context, SpvOpLabel, 0,
original_label_to_duplicate_label.at(block->id()),
opt::Instruction::OperandList()));
for (auto& instr : *block) {
// Case where an instruction is the terminator of the exit block is
// handled separately.
if (block == exit_block && instr.IsBlockTerminator()) {
switch (instr.opcode()) {
case SpvOpBranch:
case SpvOpBranchConditional:
case SpvOpReturn:
case SpvOpReturnValue:
case SpvOpUnreachable:
case SpvOpKill:
continue;
default:
assert(false &&
"Unexpected terminator for |exit_block| of the region.");
}
}
// Duplicate the instruction.
auto cloned_instr = instr.Clone(ir_context);
duplicated_block->AddInstruction(
std::unique_ptr<opt::Instruction>(cloned_instr));
if (instr.HasResultId()) {
fuzzerutil::UpdateModuleIdBound(
ir_context, original_id_to_duplicate_id.at(instr.result_id()));
}
// If an id from the original region was used in this instruction,
// replace it with the value from |original_id_to_duplicate_id|.
// If a label from the original region was used in this instruction,
// replace it with the value from |original_label_to_duplicate_label|.
cloned_instr->ForEachId(
[original_id_to_duplicate_id,
original_label_to_duplicate_label](uint32_t* op) {
if (original_id_to_duplicate_id.count(*op) != 0) {
*op = original_id_to_duplicate_id.at(*op);
} else if (original_label_to_duplicate_label.count(*op) != 0) {
*op = original_label_to_duplicate_label.at(*op);
}
});
}
// If the block is the first duplicated block, insert it after the exit
// block of the original region. Otherwise, insert it after the preceding
// one.
auto duplicated_block_ptr = duplicated_block.get();
if (previous_block) {
enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
previous_block);
} else {
enclosing_function->InsertBasicBlockAfter(std::move(duplicated_block),
exit_block);
}
previous_block = duplicated_block_ptr;
if (block == exit_block) {
// After execution of the loop, this variable stores a pointer to the last
// duplicated block.
duplicated_exit_block = duplicated_block_ptr;
}
}
for (auto& block : region_blocks) {
for (auto& instr : *block) {
if (instr.result_id() == 0) {
continue;
}
if (AvailableAfterRegion(instr, exit_block, ir_context) &&
ValidOpPhiArgument(instr, ir_context)) {
// Add an OpPhi instruction for every result id that is available at
// the end of the region, as long as the result id is valid for use
// with OpPhi.
merge_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpPhi, instr.type_id(),
original_id_to_phi_id.at(instr.result_id()),
opt::Instruction::OperandList({
{SPV_OPERAND_TYPE_ID, {instr.result_id()}},
{SPV_OPERAND_TYPE_ID, {exit_block->id()}},
{SPV_OPERAND_TYPE_ID,
{original_id_to_duplicate_id.at(instr.result_id())}},
{SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}},
})));
fuzzerutil::UpdateModuleIdBound(
ir_context, original_id_to_phi_id.at(instr.result_id()));
// If the instruction has been remapped by an OpPhi, look
// for all its uses outside of the region and outside of the
// merge block (to not overwrite just added instructions in
// the merge block) and replace the original instruction id
// with the id of the corresponding OpPhi instruction.
ir_context->get_def_use_mgr()->ForEachUse(
&instr,
[ir_context, &instr, region_blocks, original_id_to_phi_id,
&merge_block](opt::Instruction* user, uint32_t operand_index) {
auto user_block = ir_context->get_instr_block(user);
if ((region_blocks.find(user_block) != region_blocks.end()) ||
user_block == merge_block.get()) {
return;
}
user->SetOperand(operand_index,
{original_id_to_phi_id.at(instr.result_id())});
});
}
}
}
// Construct a conditional instruction in the |new_entry_block|.
// If the condition is true, the execution proceeds in the
// |entry_block| of the original region. If the condition is
// false, the execution proceeds in the first block of the
// duplicated region.
new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpSelectionMerge, 0, 0,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}},
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
{SpvSelectionControlMaskNone}}})));
new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpBranchConditional, 0, 0,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.condition_id()}},
{SPV_OPERAND_TYPE_ID, {message_.entry_block_id()}},
{SPV_OPERAND_TYPE_ID,
{original_label_to_duplicate_label.at(
message_.entry_block_id())}}})));
// Move the terminator of |exit_block| to the end of
// |merge_block|.
auto exit_block_terminator = exit_block->terminator();
auto cloned_instr = exit_block_terminator->Clone(ir_context);
merge_block->AddInstruction(std::unique_ptr<opt::Instruction>(cloned_instr));
ir_context->KillInst(exit_block_terminator);
// Add OpBranch instruction to the merge block at the end of
// |exit_block| and at the end of |duplicated_exit_block|, so that
// the execution proceeds in the |merge_block|.
opt::Instruction merge_branch_instr = opt::Instruction(
ir_context, SpvOpBranch, 0, 0,
opt::Instruction::OperandList(
{{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}}}));
exit_block->AddInstruction(MakeUnique<opt::Instruction>(merge_branch_instr));
duplicated_exit_block->AddInstruction(
std::unique_ptr<opt::Instruction>(merge_branch_instr.Clone(ir_context)));
// Execution needs to start in the |new_entry_block|. Change all
// the uses of |entry_block_label_instr| outside of the original
// region to |message_.new_entry_fresh_id|.
auto entry_block_label_instr =
ir_context->get_def_use_mgr()->GetDef(message_.entry_block_id());
ir_context->get_def_use_mgr()->ForEachUse(
entry_block_label_instr,
[this, ir_context, region_blocks](opt::Instruction* user,
uint32_t operand_index) {
auto user_block = ir_context->get_instr_block(user);
if ((region_blocks.count(user_block) != 0)) {
return;
}
switch (user->opcode()) {
case SpvOpSwitch:
case SpvOpBranch:
case SpvOpBranchConditional:
case SpvOpLoopMerge:
case SpvOpSelectionMerge: {
user->SetOperand(operand_index, {message_.new_entry_fresh_id()});
} break;
case SpvOpName:
break;
default:
assert(false &&
"The label id cannot be used by instructions "
"other than "
"OpSwitch, OpBranch, OpBranchConditional, "
"OpLoopMerge, "
"OpSelectionMerge");
}
});
opt::Instruction* merge_block_terminator = merge_block->terminator();
switch (merge_block_terminator->opcode()) {
case SpvOpReturnValue:
case SpvOpBranchConditional: {
uint32_t operand = merge_block_terminator->GetSingleWordInOperand(0);
if (original_id_to_phi_id.count(operand)) {
merge_block_terminator->SetInOperand(
0, {original_id_to_phi_id.at(operand)});
}
break;
}
default:
break;
}
// Insert the merge block after the |duplicated_exit_block| (the
// last duplicated block).
enclosing_function->InsertBasicBlockAfter(std::move(merge_block),
duplicated_exit_block);
// Insert the |new_entry_block| before the entry block of the
// original region.
enclosing_function->InsertBasicBlockBefore(std::move(new_entry_block),
entry_block);
// Since we have changed the module, most of the analysis are now
// invalid. We can invalidate analyses now after all of the blocks
// have been registered.
ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
}
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3785):
// The following method has been copied from
// TransformationOutlineFunction. Consider refactoring to avoid
// duplication.
std::set<opt::BasicBlock*>
TransformationDuplicateRegionWithSelection::GetRegionBlocks(
opt::IRContext* ir_context, opt::BasicBlock* entry_block,
opt::BasicBlock* exit_block) {
auto enclosing_function = entry_block->GetParent();
auto dominator_analysis =
ir_context->GetDominatorAnalysis(enclosing_function);
auto postdominator_analysis =
ir_context->GetPostDominatorAnalysis(enclosing_function);
// A block belongs to a region between the entry block and the exit
// block if and only if it is dominated by the entry block and
// post-dominated by the exit block.
std::set<opt::BasicBlock*> result;
for (auto& block : *enclosing_function) {
if (dominator_analysis->Dominates(entry_block, &block) &&
postdominator_analysis->Dominates(exit_block, &block)) {
result.insert(&block);
}
}
return result;
}
protobufs::Transformation
TransformationDuplicateRegionWithSelection::ToMessage() const {
protobufs::Transformation result;
*result.mutable_duplicate_region_with_selection() = message_;
return result;
}
std::unordered_set<uint32_t>
TransformationDuplicateRegionWithSelection::GetFreshIds() const {
std::unordered_set<uint32_t> result = {message_.new_entry_fresh_id(),
message_.merge_label_fresh_id()};
for (auto& pair : message_.original_label_to_duplicate_label()) {
result.insert(pair.second());
}
for (auto& pair : message_.original_id_to_duplicate_id()) {
result.insert(pair.second());
}
for (auto& pair : message_.original_id_to_phi_id()) {
result.insert(pair.second());
}
return result;
}
bool TransformationDuplicateRegionWithSelection::AvailableAfterRegion(
const opt::Instruction& instr, opt::BasicBlock* exit_block,
opt::IRContext* ir_context) {
opt::Instruction* final_instruction_in_region = &*exit_block->tail();
return &instr == final_instruction_in_region ||
fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, final_instruction_in_region, instr.result_id());
}
bool TransformationDuplicateRegionWithSelection::ValidOpPhiArgument(
const opt::Instruction& instr, opt::IRContext* ir_context) {
opt::Instruction* instr_type =
ir_context->get_def_use_mgr()->GetDef(instr.type_id());
// It is invalid to apply OpPhi to void-typed values.
if (instr_type->opcode() == SpvOpTypeVoid) {
return false;
}
// Using pointers with OpPhi requires capability VariablePointers.
if (instr_type->opcode() == SpvOpTypePointer &&
!ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityVariablePointers)) {
return false;
}
// OpTypeSampledImage cannot be the result type of an OpPhi instruction.
if (instr_type->opcode() == SpvOpTypeSampledImage) {
return false;
}
return true;
}
} // namespace fuzz
} // namespace spvtools