spirv-fuzz: Add TransformationDuplicateRegionWithSelection (#3773)

Adds a transformation that inserts a conditional statement with a
boolean expression of arbitrary value and duplicates a given
single-entry, single-exit region, so that it is present in each
conditional branch and will be executed regardless of which branch will
be taken.

Fixes #3614.
This commit is contained in:
Antoni Karpiński 2020-09-11 10:48:19 +00:00 committed by GitHub
parent 5dcb576b69
commit 244e6c1be6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 2505 additions and 1 deletions

View File

@ -80,6 +80,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_construct_composites.h
fuzzer_pass_copy_objects.h
fuzzer_pass_donate_modules.h
fuzzer_pass_duplicate_regions_with_selections.h
fuzzer_pass_inline_functions.h
fuzzer_pass_invert_comparison_operators.h
fuzzer_pass_interchange_signedness_of_integer_operands.h
@ -156,6 +157,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_composite_insert.h
transformation_compute_data_synonym_fact_closure.h
transformation_context.h
transformation_duplicate_region_with_selection.h
transformation_equation_instruction.h
transformation_function_call.h
transformation_inline_function.h
@ -241,6 +243,7 @@ if(SPIRV_BUILD_FUZZER)
fuzzer_pass_construct_composites.cpp
fuzzer_pass_copy_objects.cpp
fuzzer_pass_donate_modules.cpp
fuzzer_pass_duplicate_regions_with_selections.cpp
fuzzer_pass_inline_functions.cpp
fuzzer_pass_invert_comparison_operators.cpp
fuzzer_pass_interchange_signedness_of_integer_operands.cpp
@ -316,7 +319,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_composite_insert.cpp
transformation_compute_data_synonym_fact_closure.cpp
transformation_context.cpp
transformation_replace_opselect_with_conditional_branch.cpp
transformation_duplicate_region_with_selection.cpp
transformation_equation_instruction.cpp
transformation_function_call.cpp
transformation_inline_function.cpp
@ -343,6 +346,7 @@ if(SPIRV_BUILD_FUZZER)
transformation_replace_linear_algebra_instruction.cpp
transformation_replace_load_store_with_copy_memory.cpp
transformation_replace_opphi_id_from_dead_predecessor.cpp
transformation_replace_opselect_with_conditional_branch.cpp
transformation_replace_parameter_with_global.cpp
transformation_replace_params_with_struct.cpp
transformation_set_function_control.cpp

View File

@ -49,6 +49,7 @@
#include "source/fuzz/fuzzer_pass_construct_composites.h"
#include "source/fuzz/fuzzer_pass_copy_objects.h"
#include "source/fuzz/fuzzer_pass_donate_modules.h"
#include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h"
#include "source/fuzz/fuzzer_pass_inline_functions.h"
#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
@ -272,6 +273,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
MaybeAddPass<FuzzerPassDonateModules>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out, donor_suppliers);
MaybeAddPass<FuzzerPassDuplicateRegionsWithSelections>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
MaybeAddPass<FuzzerPassInlineFunctions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);

View File

@ -70,6 +70,8 @@ const std::pair<uint32_t, uint32_t> kChanceOfChoosingWorkgroupStorageClass = {
const std::pair<uint32_t, uint32_t> kChanceOfConstructingComposite = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
@ -230,6 +232,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator,
chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
chance_of_donating_additional_module_ =
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
chance_of_duplicating_region_with_selection_ =
ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection);
chance_of_going_deeper_to_insert_in_composite_ =
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
chance_of_going_deeper_when_making_access_chain_ =

View File

@ -198,6 +198,9 @@ class FuzzerContext {
uint32_t GetChanceOfDonatingAdditionalModule() {
return chance_of_donating_additional_module_;
}
uint32_t GetChanceOfDuplicatingRegionWithSelection() {
return chance_of_duplicating_region_with_selection_;
}
uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
return chance_of_going_deeper_to_insert_in_composite_;
}
@ -406,6 +409,7 @@ class FuzzerContext {
uint32_t chance_of_constructing_composite_;
uint32_t chance_of_copying_object_;
uint32_t chance_of_donating_additional_module_;
uint32_t chance_of_duplicating_region_with_selection_;
uint32_t chance_of_going_deeper_to_insert_in_composite_;
uint32_t chance_of_going_deeper_when_making_access_chain_;
uint32_t chance_of_inlining_function_;

View File

@ -0,0 +1,133 @@
// 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/fuzzer_pass_duplicate_regions_with_selections.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/transformation_duplicate_region_with_selection.h"
namespace spvtools {
namespace fuzz {
FuzzerPassDuplicateRegionsWithSelections::
FuzzerPassDuplicateRegionsWithSelections(
opt::IRContext* ir_context,
TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations)
: FuzzerPass(ir_context, transformation_context, fuzzer_context,
transformations) {}
FuzzerPassDuplicateRegionsWithSelections::
~FuzzerPassDuplicateRegionsWithSelections() = default;
void FuzzerPassDuplicateRegionsWithSelections::Apply() {
// Iterate over all of the functions in the module.
for (auto& function : *GetIRContext()->module()) {
// Randomly decide whether to apply the transformation.
if (!GetFuzzerContext()->ChoosePercentage(
GetFuzzerContext()->GetChanceOfDuplicatingRegionWithSelection())) {
continue;
}
std::vector<opt::BasicBlock*> candidate_entry_blocks;
for (auto& block : function) {
// We don't consider the first block to be the entry block, since it
// could contain OpVariable instructions that would require additional
// operations to be reassigned.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3778):
// Consider extending this fuzzer pass to allow the first block to be
// used in duplication.
if (&block == &*function.begin()) {
continue;
}
candidate_entry_blocks.push_back(&block);
}
if (candidate_entry_blocks.empty()) {
continue;
}
// Randomly choose the entry block.
auto entry_block = candidate_entry_blocks[GetFuzzerContext()->RandomIndex(
candidate_entry_blocks)];
auto dominator_analysis = GetIRContext()->GetDominatorAnalysis(&function);
auto postdominator_analysis =
GetIRContext()->GetPostDominatorAnalysis(&function);
std::vector<opt::BasicBlock*> candidate_exit_blocks;
for (auto postdominates_entry_block = entry_block;
postdominates_entry_block != nullptr;
postdominates_entry_block = postdominator_analysis->ImmediateDominator(
postdominates_entry_block)) {
// The candidate exit block must be dominated by the entry block and the
// entry block must be post-dominated by the candidate exit block. Ignore
// the block if it heads a selection construct or a loop construct.
if (dominator_analysis->Dominates(entry_block,
postdominates_entry_block) &&
!postdominates_entry_block->GetLoopMergeInst()) {
candidate_exit_blocks.push_back(postdominates_entry_block);
}
}
if (candidate_exit_blocks.empty()) {
continue;
}
// Randomly choose the exit block.
auto exit_block = candidate_exit_blocks[GetFuzzerContext()->RandomIndex(
candidate_exit_blocks)];
auto region_blocks =
TransformationDuplicateRegionWithSelection::GetRegionBlocks(
GetIRContext(), entry_block, exit_block);
// Construct |original_label_to_duplicate_label| by iterating over all
// blocks in the region. Construct |original_id_to_duplicate_id| and
// |original_id_to_phi_id| by iterating over all instructions in each block.
std::map<uint32_t, uint32_t> original_label_to_duplicate_label;
std::map<uint32_t, uint32_t> original_id_to_duplicate_id;
std::map<uint32_t, uint32_t> original_id_to_phi_id;
for (auto& block : region_blocks) {
original_label_to_duplicate_label[block->id()] =
GetFuzzerContext()->GetFreshId();
for (auto& instr : *block) {
if (instr.result_id()) {
original_id_to_duplicate_id[instr.result_id()] =
GetFuzzerContext()->GetFreshId();
auto final_instruction = &*exit_block->tail();
// &*exit_block->tail() is the final instruction of the region.
// The instruction is available at the end of the region if and only
// if it is available before this final instruction or it is the final
// instruction.
if ((&instr == final_instruction ||
fuzzerutil::IdIsAvailableBeforeInstruction(
GetIRContext(), final_instruction, instr.result_id()))) {
original_id_to_phi_id[instr.result_id()] =
GetFuzzerContext()->GetFreshId();
}
}
}
}
// Randomly decide between value "true" or "false" for a bool constant.
// Make sure the transformation has access to a bool constant to be used
// while creating conditional construct.
auto condition_id =
FindOrCreateBoolConstant(GetFuzzerContext()->ChooseEven(), true);
TransformationDuplicateRegionWithSelection transformation =
TransformationDuplicateRegionWithSelection(
GetFuzzerContext()->GetFreshId(), condition_id,
GetFuzzerContext()->GetFreshId(), entry_block->id(),
exit_block->id(), std::move(original_label_to_duplicate_label),
std::move(original_id_to_duplicate_id),
std::move(original_id_to_phi_id));
MaybeApplyTransformation(transformation);
}
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,42 @@
// 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.
#ifndef SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_
#define SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_
#include "source/fuzz/fuzzer_pass.h"
namespace spvtools {
namespace fuzz {
// A fuzzer pass that iterates over the whole module. For each function it
// finds a single-entry, single-exit region with its constructs and their merge
// blocks either completely within or completely outside the region. It
// duplicates this region using the corresponding transformation.
class FuzzerPassDuplicateRegionsWithSelections : public FuzzerPass {
public:
FuzzerPassDuplicateRegionsWithSelections(
opt::IRContext* ir_context, TransformationContext* transformation_context,
FuzzerContext* fuzzer_context,
protobufs::TransformationSequence* transformations);
~FuzzerPassDuplicateRegionsWithSelections() override;
void Apply() override;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_FUZZER_PASS_DUPLICATE_REGIONS_WITH_SELECTIONS_H_

View File

@ -419,6 +419,7 @@ message Transformation {
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75;
// Add additional option using the next available number.
}
}
@ -1082,6 +1083,41 @@ message TransformationComputeDataSynonymFactClosure {
}
message TransformationDuplicateRegionWithSelection {
// A transformation that inserts a conditional statement with a boolean expression
// of arbitrary value and duplicates a given single-entry, single-exit region, so
// that it is present in each conditional branch and will be executed regardless
// of which branch will be taken.
// Fresh id for a label of the new entry block.
uint32 new_entry_fresh_id = 1;
// Id for a boolean expression.
uint32 condition_id = 2;
// Fresh id for a label of the merge block of the conditional.
uint32 merge_label_fresh_id = 3;
// Block id of the entry block of the original region.
uint32 entry_block_id = 4;
// Block id of the exit block of the original region.
uint32 exit_block_id = 5;
// Map that maps from a label in the original region to the corresponding label
// in the duplicated region.
repeated UInt32Pair original_label_to_duplicate_label = 6;
// Map that maps from a result id in the original region to the corresponding
// result id in the duplicated region.
repeated UInt32Pair original_id_to_duplicate_id = 7;
// Map that maps from a result id in the original region to the result id of the
// corresponding OpPhi instruction.
repeated UInt32Pair original_id_to_phi_id = 8;
}
message TransformationEquationInstruction {
// A transformation that adds an instruction to the module that defines an

View File

@ -52,6 +52,7 @@
#include "source/fuzz/transformation_composite_extract.h"
#include "source/fuzz/transformation_composite_insert.h"
#include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
#include "source/fuzz/transformation_duplicate_region_with_selection.h"
#include "source/fuzz/transformation_equation_instruction.h"
#include "source/fuzz/transformation_function_call.h"
#include "source/fuzz/transformation_inline_function.h"
@ -196,6 +197,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
kComputeDataSynonymFactClosure:
return MakeUnique<TransformationComputeDataSynonymFactClosure>(
message.compute_data_synonym_fact_closure());
case protobufs::Transformation::TransformationCase::
kDuplicateRegionWithSelection:
return MakeUnique<TransformationDuplicateRegionWithSelection>(
message.duplicate_region_with_selection());
case protobufs::Transformation::TransformationCase::kEquationInstruction:
return MakeUnique<TransformationEquationInstruction>(
message.equation_instruction());

View File

@ -0,0 +1,589 @@
// 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(
const spvtools::fuzz::protobufs::
TransformationDuplicateRegionWithSelection& message)
: message_(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& /*unused*/) 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;
}
// 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.
if (continue_target != exit_block &&
region_set.count(&block) != region_set.count(continue_target)) {
return false;
}
}
}
// Get the maps from the protobuf.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3786):
// Consider additionally providing overflow ids to make this
// transformation more applicable when shrinking.
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) {
auto label =
ir_context->get_def_use_mgr()->GetDef(block->id())->result_id();
// The label of every block in the region must be present in the map
// |original_label_to_duplicate_label|.
if (original_label_to_duplicate_label.count(label) == 0) {
return false;
}
auto duplicate_label = original_label_to_duplicate_label[label];
// 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|.
if (original_id_to_duplicate_id.count(instr.result_id()) == 0) {
return false;
}
auto duplicate_id = original_id_to_duplicate_id[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 (&instr == &*exit_block->tail() ||
fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, &*exit_block->tail(), instr.result_id())) {
// Every instruction with a result id available at the end of the region
// must be present in the map |original_id_to_phi_id|.
if (original_id_to_phi_id.count(instr.result_id()) == 0) {
return false;
}
// Using pointers with OpPhi requires capability VariablePointers.
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3787):
// Consider not adding OpPhi instructions for the pointers which are
// unused after the region, so that the transformation could be
// still applicable.
if (ir_context->get_type_mgr()->GetType(instr.type_id())->AsPointer() &&
!ir_context->get_feature_mgr()->HasCapability(
SpvCapabilityVariablePointers)) {
return false;
}
auto phi_id = original_id_to_phi_id[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* /*unused*/) 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>(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());
// 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
// occurence 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;
// 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[block->id()]);
std::unique_ptr<opt::BasicBlock> duplicated_block =
MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
ir_context, SpvOpLabel, 0,
original_label_to_duplicate_label[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 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));
fuzzerutil::UpdateModuleIdBound(
ir_context, original_id_to_duplicate_id[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);
}
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;
}
// After execution of the loop, this variable stores a pointer to the last
// duplicated block.
auto duplicated_exit_block = previous_block;
for (auto& block : region_blocks) {
for (auto& instr : *block) {
if (instr.result_id() != 0 &&
(&instr == &*exit_block->tail() ||
fuzzerutil::IdIsAvailableBeforeInstruction(
ir_context, &*exit_block->tail(), instr.result_id()))) {
// Add the OpPhi instruction for every result id that is
// available at the end of the region (the last instruction
// of the |exit_block|)
merge_block->AddInstruction(MakeUnique<opt::Instruction>(
ir_context, SpvOpPhi, instr.type_id(),
original_id_to_phi_id[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[instr.result_id()]}},
{SPV_OPERAND_TYPE_ID, {duplicated_exit_block->id()}},
})));
fuzzerutil::UpdateModuleIdBound(
ir_context, original_id_to_phi_id[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[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(
MakeUnique<opt::Instruction>(merge_branch_instr));
// 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");
}
});
// 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;
}
} // namespace fuzz
} // namespace spvtools

View File

@ -0,0 +1,78 @@
// 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.
#ifndef SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_
#define SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
#include "source/fuzz/transformation.h"
#include "source/fuzz/transformation_context.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace fuzz {
class TransformationDuplicateRegionWithSelection : public Transformation {
public:
explicit TransformationDuplicateRegionWithSelection(
const protobufs::TransformationDuplicateRegionWithSelection& message);
explicit 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);
// - |new_entry_fresh_id|, |merge_label_fresh_id| must be fresh and distinct.
// - |condition_id| must refer to a valid instruction of boolean type.
// - |entry_block_id| and |exit_block_id| must refer to valid blocks and they
// must form a single-entry, single-exit region. Its constructs and their
// merge blocks must be either wholly within or wholly outside of the
// region.
// - |original_label_to_duplicate_label| must contain at least a key for every
// block in the original region.
// - |original_id_to_duplicate_id| must contain at least a key for every
// result id in the original region.
// - |original_id_to_phi_id| must contain at least a key for every result id
// available at the end of the original region.
// - In each of these three maps, each value must be a distinct, fresh id.
bool IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const override;
// A transformation that inserts a conditional statement with a boolean
// expression of arbitrary value and duplicates a given single-entry,
// single-exit region, so that it is present in each conditional branch and
// will be executed regardless of which branch will be taken.
void Apply(opt::IRContext* ir_context,
TransformationContext* transformation_context) const override;
// Returns the set of blocks dominated by |entry_block| and post-dominated
// by |exit_block|.
static std::set<opt::BasicBlock*> GetRegionBlocks(
opt::IRContext* ir_context, opt::BasicBlock* entry_block,
opt::BasicBlock* exit_block);
protobufs::Transformation ToMessage() const override;
private:
protobufs::TransformationDuplicateRegionWithSelection message_;
};
} // namespace fuzz
} // namespace spvtools
#endif // SOURCE_FUZZ_TRANSFORMATION_DUPLICATE_REGION_WITH_SELECTION_H_

View File

@ -62,6 +62,7 @@ if (${SPIRV_BUILD_FUZZER})
transformation_composite_extract_test.cpp
transformation_composite_insert_test.cpp
transformation_compute_data_synonym_fact_closure_test.cpp
transformation_duplicate_region_with_selection_test.cpp
transformation_equation_instruction_test.cpp
transformation_function_call_test.cpp
transformation_inline_function_test.cpp

File diff suppressed because it is too large Load Diff