mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 22:41:03 +00:00
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:
parent
5dcb576b69
commit
244e6c1be6
@ -80,6 +80,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||||||
fuzzer_pass_construct_composites.h
|
fuzzer_pass_construct_composites.h
|
||||||
fuzzer_pass_copy_objects.h
|
fuzzer_pass_copy_objects.h
|
||||||
fuzzer_pass_donate_modules.h
|
fuzzer_pass_donate_modules.h
|
||||||
|
fuzzer_pass_duplicate_regions_with_selections.h
|
||||||
fuzzer_pass_inline_functions.h
|
fuzzer_pass_inline_functions.h
|
||||||
fuzzer_pass_invert_comparison_operators.h
|
fuzzer_pass_invert_comparison_operators.h
|
||||||
fuzzer_pass_interchange_signedness_of_integer_operands.h
|
fuzzer_pass_interchange_signedness_of_integer_operands.h
|
||||||
@ -156,6 +157,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||||||
transformation_composite_insert.h
|
transformation_composite_insert.h
|
||||||
transformation_compute_data_synonym_fact_closure.h
|
transformation_compute_data_synonym_fact_closure.h
|
||||||
transformation_context.h
|
transformation_context.h
|
||||||
|
transformation_duplicate_region_with_selection.h
|
||||||
transformation_equation_instruction.h
|
transformation_equation_instruction.h
|
||||||
transformation_function_call.h
|
transformation_function_call.h
|
||||||
transformation_inline_function.h
|
transformation_inline_function.h
|
||||||
@ -241,6 +243,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||||||
fuzzer_pass_construct_composites.cpp
|
fuzzer_pass_construct_composites.cpp
|
||||||
fuzzer_pass_copy_objects.cpp
|
fuzzer_pass_copy_objects.cpp
|
||||||
fuzzer_pass_donate_modules.cpp
|
fuzzer_pass_donate_modules.cpp
|
||||||
|
fuzzer_pass_duplicate_regions_with_selections.cpp
|
||||||
fuzzer_pass_inline_functions.cpp
|
fuzzer_pass_inline_functions.cpp
|
||||||
fuzzer_pass_invert_comparison_operators.cpp
|
fuzzer_pass_invert_comparison_operators.cpp
|
||||||
fuzzer_pass_interchange_signedness_of_integer_operands.cpp
|
fuzzer_pass_interchange_signedness_of_integer_operands.cpp
|
||||||
@ -316,7 +319,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||||||
transformation_composite_insert.cpp
|
transformation_composite_insert.cpp
|
||||||
transformation_compute_data_synonym_fact_closure.cpp
|
transformation_compute_data_synonym_fact_closure.cpp
|
||||||
transformation_context.cpp
|
transformation_context.cpp
|
||||||
transformation_replace_opselect_with_conditional_branch.cpp
|
transformation_duplicate_region_with_selection.cpp
|
||||||
transformation_equation_instruction.cpp
|
transformation_equation_instruction.cpp
|
||||||
transformation_function_call.cpp
|
transformation_function_call.cpp
|
||||||
transformation_inline_function.cpp
|
transformation_inline_function.cpp
|
||||||
@ -343,6 +346,7 @@ if(SPIRV_BUILD_FUZZER)
|
|||||||
transformation_replace_linear_algebra_instruction.cpp
|
transformation_replace_linear_algebra_instruction.cpp
|
||||||
transformation_replace_load_store_with_copy_memory.cpp
|
transformation_replace_load_store_with_copy_memory.cpp
|
||||||
transformation_replace_opphi_id_from_dead_predecessor.cpp
|
transformation_replace_opphi_id_from_dead_predecessor.cpp
|
||||||
|
transformation_replace_opselect_with_conditional_branch.cpp
|
||||||
transformation_replace_parameter_with_global.cpp
|
transformation_replace_parameter_with_global.cpp
|
||||||
transformation_replace_params_with_struct.cpp
|
transformation_replace_params_with_struct.cpp
|
||||||
transformation_set_function_control.cpp
|
transformation_set_function_control.cpp
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
#include "source/fuzz/fuzzer_pass_construct_composites.h"
|
#include "source/fuzz/fuzzer_pass_construct_composites.h"
|
||||||
#include "source/fuzz/fuzzer_pass_copy_objects.h"
|
#include "source/fuzz/fuzzer_pass_copy_objects.h"
|
||||||
#include "source/fuzz/fuzzer_pass_donate_modules.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_inline_functions.h"
|
||||||
#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
|
#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
|
||||||
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
|
#include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
|
||||||
@ -272,6 +273,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run(
|
|||||||
MaybeAddPass<FuzzerPassDonateModules>(
|
MaybeAddPass<FuzzerPassDonateModules>(
|
||||||
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||||
transformation_sequence_out, donor_suppliers);
|
transformation_sequence_out, donor_suppliers);
|
||||||
|
MaybeAddPass<FuzzerPassDuplicateRegionsWithSelections>(
|
||||||
|
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||||
|
transformation_sequence_out);
|
||||||
MaybeAddPass<FuzzerPassInlineFunctions>(
|
MaybeAddPass<FuzzerPassInlineFunctions>(
|
||||||
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
|
||||||
transformation_sequence_out);
|
transformation_sequence_out);
|
||||||
|
@ -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> kChanceOfConstructingComposite = {20, 50};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfCopyingObject = {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> kChanceOfDonatingAdditionalModule = {5, 50};
|
||||||
|
const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
|
||||||
|
20, 50};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
|
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToInsertInComposite = {
|
||||||
30, 70};
|
30, 70};
|
||||||
const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperWhenMakingAccessChain =
|
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_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject);
|
||||||
chance_of_donating_additional_module_ =
|
chance_of_donating_additional_module_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
|
||||||
|
chance_of_duplicating_region_with_selection_ =
|
||||||
|
ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection);
|
||||||
chance_of_going_deeper_to_insert_in_composite_ =
|
chance_of_going_deeper_to_insert_in_composite_ =
|
||||||
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
|
ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite);
|
||||||
chance_of_going_deeper_when_making_access_chain_ =
|
chance_of_going_deeper_when_making_access_chain_ =
|
||||||
|
@ -198,6 +198,9 @@ class FuzzerContext {
|
|||||||
uint32_t GetChanceOfDonatingAdditionalModule() {
|
uint32_t GetChanceOfDonatingAdditionalModule() {
|
||||||
return chance_of_donating_additional_module_;
|
return chance_of_donating_additional_module_;
|
||||||
}
|
}
|
||||||
|
uint32_t GetChanceOfDuplicatingRegionWithSelection() {
|
||||||
|
return chance_of_duplicating_region_with_selection_;
|
||||||
|
}
|
||||||
uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
|
uint32_t GetChanceOfGoingDeeperToInsertInComposite() {
|
||||||
return chance_of_going_deeper_to_insert_in_composite_;
|
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_constructing_composite_;
|
||||||
uint32_t chance_of_copying_object_;
|
uint32_t chance_of_copying_object_;
|
||||||
uint32_t chance_of_donating_additional_module_;
|
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_to_insert_in_composite_;
|
||||||
uint32_t chance_of_going_deeper_when_making_access_chain_;
|
uint32_t chance_of_going_deeper_when_making_access_chain_;
|
||||||
uint32_t chance_of_inlining_function_;
|
uint32_t chance_of_inlining_function_;
|
||||||
|
133
source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
Normal file
133
source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
Normal 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
|
42
source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
Normal file
42
source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
Normal 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_
|
@ -419,6 +419,7 @@ message Transformation {
|
|||||||
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
|
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
|
||||||
TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
|
TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
|
||||||
TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
|
TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
|
||||||
|
TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75;
|
||||||
// Add additional option using the next available number.
|
// 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 {
|
message TransformationEquationInstruction {
|
||||||
|
|
||||||
// A transformation that adds an instruction to the module that defines an
|
// A transformation that adds an instruction to the module that defines an
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
#include "source/fuzz/transformation_composite_extract.h"
|
#include "source/fuzz/transformation_composite_extract.h"
|
||||||
#include "source/fuzz/transformation_composite_insert.h"
|
#include "source/fuzz/transformation_composite_insert.h"
|
||||||
#include "source/fuzz/transformation_compute_data_synonym_fact_closure.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_equation_instruction.h"
|
||||||
#include "source/fuzz/transformation_function_call.h"
|
#include "source/fuzz/transformation_function_call.h"
|
||||||
#include "source/fuzz/transformation_inline_function.h"
|
#include "source/fuzz/transformation_inline_function.h"
|
||||||
@ -196,6 +197,10 @@ std::unique_ptr<Transformation> Transformation::FromMessage(
|
|||||||
kComputeDataSynonymFactClosure:
|
kComputeDataSynonymFactClosure:
|
||||||
return MakeUnique<TransformationComputeDataSynonymFactClosure>(
|
return MakeUnique<TransformationComputeDataSynonymFactClosure>(
|
||||||
message.compute_data_synonym_fact_closure());
|
message.compute_data_synonym_fact_closure());
|
||||||
|
case protobufs::Transformation::TransformationCase::
|
||||||
|
kDuplicateRegionWithSelection:
|
||||||
|
return MakeUnique<TransformationDuplicateRegionWithSelection>(
|
||||||
|
message.duplicate_region_with_selection());
|
||||||
case protobufs::Transformation::TransformationCase::kEquationInstruction:
|
case protobufs::Transformation::TransformationCase::kEquationInstruction:
|
||||||
return MakeUnique<TransformationEquationInstruction>(
|
return MakeUnique<TransformationEquationInstruction>(
|
||||||
message.equation_instruction());
|
message.equation_instruction());
|
||||||
|
589
source/fuzz/transformation_duplicate_region_with_selection.cpp
Normal file
589
source/fuzz/transformation_duplicate_region_with_selection.cpp
Normal 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,
|
||||||
|
®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.
|
||||||
|
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
|
78
source/fuzz/transformation_duplicate_region_with_selection.h
Normal file
78
source/fuzz/transformation_duplicate_region_with_selection.h
Normal 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_
|
@ -62,6 +62,7 @@ if (${SPIRV_BUILD_FUZZER})
|
|||||||
transformation_composite_extract_test.cpp
|
transformation_composite_extract_test.cpp
|
||||||
transformation_composite_insert_test.cpp
|
transformation_composite_insert_test.cpp
|
||||||
transformation_compute_data_synonym_fact_closure_test.cpp
|
transformation_compute_data_synonym_fact_closure_test.cpp
|
||||||
|
transformation_duplicate_region_with_selection_test.cpp
|
||||||
transformation_equation_instruction_test.cpp
|
transformation_equation_instruction_test.cpp
|
||||||
transformation_function_call_test.cpp
|
transformation_function_call_test.cpp
|
||||||
transformation_inline_function_test.cpp
|
transformation_inline_function_test.cpp
|
||||||
|
1604
test/fuzz/transformation_duplicate_region_with_selection_test.cpp
Normal file
1604
test/fuzz/transformation_duplicate_region_with_selection_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user