mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 14:31:04 +00:00
d35a78db57
Fixes #4960 * Switches to using enum classes with an underlying type to avoid undefined behaviour
722 lines
29 KiB
C++
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() != spv::Op::OpLabel) {
|
|
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,
|
|
®ion_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,
|
|
®ion_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, spv::Op::OpLabel, 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, spv::Op::OpLabel, 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 overflow 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() == spv::Op::OpPhi) {
|
|
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() == spv::Op::OpPhi) {
|
|
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, spv::Op::OpLabel, 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 spv::Op::OpBranch:
|
|
case spv::Op::OpBranchConditional:
|
|
case spv::Op::OpReturn:
|
|
case spv::Op::OpReturnValue:
|
|
case spv::Op::OpUnreachable:
|
|
case spv::Op::OpKill:
|
|
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, spv::Op::OpPhi, 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, spv::Op::OpSelectionMerge, 0, 0,
|
|
opt::Instruction::OperandList(
|
|
{{SPV_OPERAND_TYPE_ID, {message_.merge_label_fresh_id()}},
|
|
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
|
|
{uint32_t(spv::SelectionControlMask::MaskNone)}}})));
|
|
|
|
new_entry_block->AddInstruction(MakeUnique<opt::Instruction>(
|
|
ir_context, spv::Op::OpBranchConditional, 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, spv::Op::OpBranch, 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 spv::Op::OpSwitch:
|
|
case spv::Op::OpBranch:
|
|
case spv::Op::OpBranchConditional:
|
|
case spv::Op::OpLoopMerge:
|
|
case spv::Op::OpSelectionMerge: {
|
|
user->SetOperand(operand_index, {message_.new_entry_fresh_id()});
|
|
} break;
|
|
case spv::Op::OpName:
|
|
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 spv::Op::OpReturnValue:
|
|
case spv::Op::OpBranchConditional: {
|
|
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() == spv::Op::OpTypeVoid) {
|
|
return false;
|
|
}
|
|
|
|
// Using pointers with OpPhi requires capability VariablePointers.
|
|
if (instr_type->opcode() == spv::Op::OpTypePointer &&
|
|
!ir_context->get_feature_mgr()->HasCapability(
|
|
spv::Capability::VariablePointers)) {
|
|
return false;
|
|
}
|
|
|
|
// OpTypeSampledImage cannot be the result type of an OpPhi instruction.
|
|
if (instr_type->opcode() == spv::Op::OpTypeSampledImage) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace fuzz
|
|
} // namespace spvtools
|