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.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 4d9ecdd..e1cc925 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -80,6 +80,7 @@
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 @@
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 @@
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 @@
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 @@
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
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index ee638b6..8b9c9cb 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -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 @@
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);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 3443a14..869cb5a 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -70,6 +70,8 @@
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 @@
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_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 7427a41..8f045c3 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -198,6 +198,9 @@
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 @@
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_;
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
new file mode 100644
index 0000000..45b51ec
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.cpp
@@ -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
diff --git a/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
new file mode 100644
index 0000000..3fae698
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h
@@ -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_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 5d27761..f1fa6ac 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -419,6 +419,7 @@
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 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
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 7156f6e..2f00dc3 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -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 @@
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());
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.cpp b/source/fuzz/transformation_duplicate_region_with_selection.cpp
new file mode 100644
index 0000000..3b6c1df
--- /dev/null
+++ b/source/fuzz/transformation_duplicate_region_with_selection.cpp
@@ -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
diff --git a/source/fuzz/transformation_duplicate_region_with_selection.h b/source/fuzz/transformation_duplicate_region_with_selection.h
new file mode 100644
index 0000000..a1a2a89
--- /dev/null
+++ b/source/fuzz/transformation_duplicate_region_with_selection.h
@@ -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_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 6199e69..f4add4c 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -62,6 +62,7 @@
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
diff --git a/test/fuzz/transformation_duplicate_region_with_selection_test.cpp b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp
new file mode 100644
index 0000000..6539c4a
--- /dev/null
+++ b/test/fuzz/transformation_duplicate_region_with_selection_test.cpp
@@ -0,0 +1,1604 @@
+// 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 "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationDuplicateRegionWithSelectionTest, BasicUseTest) {
+ // This test handles a case where the ids from the original region are used in
+ // subsequent block.
+
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "b"
+ OpName %18 "c"
+ OpName %20 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %14 = OpConstant %6 2
+ %16 = OpTypeBool
+ %17 = OpTypePointer Function %16
+ %19 = OpConstantTrue %16
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %18 = OpVariable %17 Function
+ %20 = OpVariable %7 Function
+ OpStore %18 %19
+ OpStore %20 %14
+ %21 = OpFunctionCall %2 %10 %20
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %800
+ %800 = OpLabel
+ %13 = OpLoad %6 %9
+ %15 = OpIAdd %6 %13 %14
+ OpStore %12 %15
+ OpBranch %900
+ %900 = OpLabel
+ %901 = OpIAdd %6 %15 %13
+ %902 = OpISub %6 %13 %15
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+ std::string expected_shader = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "b"
+ OpName %18 "c"
+ OpName %20 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %14 = OpConstant %6 2
+ %16 = OpTypeBool
+ %17 = OpTypePointer Function %16
+ %19 = OpConstantTrue %16
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %18 = OpVariable %17 Function
+ %20 = OpVariable %7 Function
+ OpStore %18 %19
+ OpStore %20 %14
+ %21 = OpFunctionCall %2 %10 %20
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %19 %800 %100
+ %800 = OpLabel
+ %13 = OpLoad %6 %9
+ %15 = OpIAdd %6 %13 %14
+ OpStore %12 %15
+ OpBranch %501
+ %100 = OpLabel
+ %201 = OpLoad %6 %9
+ %202 = OpIAdd %6 %201 %14
+ OpStore %12 %202
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %6 %13 %800 %201 %100
+ %302 = OpPhi %6 %15 %800 %202 %100
+ OpBranch %900
+ %900 = OpLabel
+ %901 = OpIAdd %6 %302 %301
+ %902 = OpISub %6 %301 %302
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, BasicExitBlockTest) {
+ // This test handles a case where the exit block of the region is the exit
+ // block of the containing function.
+
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "b"
+ OpName %18 "c"
+ OpName %20 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %14 = OpConstant %6 2
+ %16 = OpTypeBool
+ %17 = OpTypePointer Function %16
+ %19 = OpConstantTrue %16
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %18 = OpVariable %17 Function
+ %20 = OpVariable %7 Function
+ OpStore %18 %19
+ OpStore %20 %14
+ %21 = OpFunctionCall %2 %10 %20
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %800
+ %800 = OpLabel
+ %13 = OpLoad %6 %9
+ %15 = OpIAdd %6 %13 %14
+ OpStore %12 %15
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "b"
+ OpName %18 "c"
+ OpName %20 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %14 = OpConstant %6 2
+ %16 = OpTypeBool
+ %17 = OpTypePointer Function %16
+ %19 = OpConstantTrue %16
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %18 = OpVariable %17 Function
+ %20 = OpVariable %7 Function
+ OpStore %18 %19
+ OpStore %20 %14
+ %21 = OpFunctionCall %2 %10 %20
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %19 %800 %100
+ %800 = OpLabel
+ %13 = OpLoad %6 %9
+ %15 = OpIAdd %6 %13 %14
+ OpStore %12 %15
+ OpBranch %501
+ %100 = OpLabel
+ %201 = OpLoad %6 %9
+ %202 = OpIAdd %6 %201 %14
+ OpStore %12 %202
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %6 %13 %800 %201 %100
+ %302 = OpPhi %6 %15 %800 %202 %100
+ OpReturn
+ OpFunctionEnd
+
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest) {
+ // This test handles few cases where the transformation is not applicable
+ // because of the control flow graph or layout of the blocks.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %18 "b"
+ OpName %25 "c"
+ OpName %27 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 2
+ %14 = OpTypeBool
+ %24 = OpTypePointer Function %14
+ %26 = OpConstantTrue %14
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %25 = OpVariable %24 Function
+ %27 = OpVariable %7 Function
+ OpStore %25 %26
+ OpStore %27 %13
+ %28 = OpFunctionCall %2 %10 %27
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %18 = OpVariable %7 Function
+ %12 = OpLoad %6 %9
+ %15 = OpSLessThan %14 %12 %13
+ OpSelectionMerge %17 None
+ OpBranchConditional %15 %16 %21
+ %16 = OpLabel
+ %19 = OpLoad %6 %9
+ %20 = OpIAdd %6 %19 %13
+ OpStore %18 %20
+ OpBranch %17
+ %21 = OpLabel
+ %22 = OpLoad %6 %9
+ %23 = OpISub %6 %22 %13
+ OpStore %18 %23
+ OpBranch %17
+ %17 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Bad: |entry_block_id| refers to the entry block of the function (this
+ // transformation currently avoids such cases).
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 26, 501, 11, 11, {{11, 100}}, {{18, 201}, {12, 202}, {15, 203}},
+ {{18, 301}, {12, 302}, {15, 303}});
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+ // Bad: The block with id 16 does not dominate the block with id 21.
+ TransformationDuplicateRegionWithSelection transformation_bad_2 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 26, 501, 16, 21, {{16, 100}, {21, 101}},
+ {{19, 201}, {20, 202}, {22, 203}, {23, 204}},
+ {{19, 301}, {20, 302}, {22, 303}, {23, 304}});
+ ASSERT_FALSE(
+ transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+ // Bad: The block with id 21 does not post-dominate the block with id 11.
+ TransformationDuplicateRegionWithSelection transformation_bad_3 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 26, 501, 11, 21, {{11, 100}, {21, 101}},
+ {{18, 201}, {12, 202}, {15, 203}, {22, 204}, {23, 205}},
+ {{18, 301}, {12, 302}, {15, 303}, {22, 304}, {23, 305}});
+ ASSERT_FALSE(
+ transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+ // Bad: The block with id 5 is contained in a different function than the
+ // block with id 11.
+ TransformationDuplicateRegionWithSelection transformation_bad_4 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 26, 501, 5, 11, {{5, 100}, {11, 101}},
+ {{25, 201}, {27, 202}, {28, 203}, {18, 204}, {12, 205}, {15, 206}},
+ {{25, 301}, {27, 302}, {28, 303}, {18, 304}, {12, 305}, {15, 306}});
+ ASSERT_FALSE(
+ transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableIdTest) {
+ // This test handles a case where the supplied ids are either not fresh, not
+ // distinct, not valid in their context or do not refer to the existing
+ // instructions.
+
+ std::string shader = R"(
+ OpCapability Shader
+ OpCapability VariablePointers
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "b"
+ OpName %18 "c"
+ OpName %20 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %14 = OpConstant %6 2
+ %16 = OpTypeBool
+ %17 = OpTypePointer Function %16
+ %19 = OpConstantTrue %16
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %18 = OpVariable %17 Function
+ %20 = OpVariable %7 Function
+ OpStore %18 %19
+ OpStore %20 %14
+ %21 = OpFunctionCall %2 %10 %20
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %800
+ %800 = OpLabel
+ %13 = OpLoad %6 %9
+ %15 = OpIAdd %6 %13 %14
+ OpStore %12 %15
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Bad: A value in the |original_label_to_duplicate_label| is not a fresh id.
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 21}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+ // Bad: Values in the |original_id_to_duplicate_id| are not distinct.
+ TransformationDuplicateRegionWithSelection transformation_bad_2 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 201}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+ // Bad: Values in the |original_id_to_phi_id| are not fresh and are not
+ // distinct with previous values.
+ TransformationDuplicateRegionWithSelection transformation_bad_3 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 18}, {15, 202}});
+ ASSERT_FALSE(
+ transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+ // Bad: |entry_block_id| does not refer to an existing instruction.
+ TransformationDuplicateRegionWithSelection transformation_bad_4 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 802, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+ // Bad: |exit_block_id| does not refer to a block.
+ TransformationDuplicateRegionWithSelection transformation_bad_5 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 9, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_5.IsApplicable(context.get(), transformation_context));
+
+ // Bad: |new_entry_fresh_id| is not fresh.
+ TransformationDuplicateRegionWithSelection transformation_bad_6 =
+ TransformationDuplicateRegionWithSelection(
+ 20, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_6.IsApplicable(context.get(), transformation_context));
+
+ // Bad: |merge_label_fresh_id| is not fresh.
+ TransformationDuplicateRegionWithSelection transformation_bad_7 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 20, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_7.IsApplicable(context.get(), transformation_context));
+
+ // Bad: Instruction with id 15 is from the original region and is available
+ // at the end of the region but it is not present in the
+ // |original_id_to_phi_id|.
+ TransformationDuplicateRegionWithSelection transformation_bad_8 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 19, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}});
+ ASSERT_FALSE(
+ transformation_bad_8.IsApplicable(context.get(), transformation_context));
+
+ // Bad: Instruction with id 15 is from the original region but it is
+ // not present in the |original_id_to_duplicate_id|.
+ TransformationDuplicateRegionWithSelection transformation_bad_9 =
+ TransformationDuplicateRegionWithSelection(500, 19, 501, 800, 800,
+ {{800, 100}}, {{13, 201}},
+ {{13, 301}, {15, 302}});
+ ASSERT_FALSE(
+ transformation_bad_9.IsApplicable(context.get(), transformation_context));
+
+ // Bad: |condition_id| does not refer to the valid instruction.
+ TransformationDuplicateRegionWithSelection transformation_bad_10 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 200, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+
+ ASSERT_FALSE(transformation_bad_10.IsApplicable(context.get(),
+ transformation_context));
+
+ // Bad: |condition_id| does not refer to the instruction of type OpTypeBool
+ TransformationDuplicateRegionWithSelection transformation_bad_11 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 14, 501, 800, 800, {{800, 100}}, {{13, 201}, {15, 202}},
+ {{13, 301}, {15, 302}});
+
+ ASSERT_FALSE(transformation_bad_11.IsApplicable(context.get(),
+ transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest2) {
+ // This test handles few cases where the transformation is not applicable
+ // because of the control flow graph or the layout of the blocks.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %12 "i"
+ OpName %29 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %19 = OpConstant %8 10
+ %20 = OpTypeBool
+ %26 = OpConstant %8 1
+ %28 = OpTypePointer Function %20
+ %30 = OpConstantTrue %20
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %29 = OpVariable %28 Function
+ OpStore %29 %30
+ %31 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ %12 = OpVariable %9 Function
+ OpStore %10 %11
+ OpStore %12 %11
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %15 %16 None
+ OpBranch %17
+ %17 = OpLabel
+ %18 = OpLoad %8 %12
+ %21 = OpSLessThan %20 %18 %19
+ OpBranchConditional %21 %14 %15
+ %14 = OpLabel
+ %22 = OpLoad %8 %10
+ %23 = OpLoad %8 %12
+ %24 = OpIAdd %8 %22 %23
+ OpStore %10 %24
+ OpBranch %16
+ %16 = OpLabel
+ OpBranch %50
+ %50 = OpLabel
+ %25 = OpLoad %8 %12
+ %27 = OpIAdd %8 %25 %26
+ OpStore %12 %27
+ OpBranch %13
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+ // Bad: The exit block cannot be a header of a loop, because the region won't
+ // be a single-entry, single-exit region.
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(500, 30, 501, 13, 13,
+ {{13, 100}}, {{}}, {{}});
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+ // Bad: The block with id 13, the loop header, is in the region. The block
+ // with id 15, the loop merge block, is not in the region.
+ TransformationDuplicateRegionWithSelection transformation_bad_2 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 30, 501, 13, 17, {{13, 100}, {17, 101}}, {{18, 201}, {21, 202}},
+ {{18, 301}, {21, 302}});
+ ASSERT_FALSE(
+ transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+ // Bad: The block with id 13, the loop header, is not in the region. The block
+ // with id 16, the loop continue target, is in the region.
+ TransformationDuplicateRegionWithSelection transformation_bad_3 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 30, 501, 16, 50, {{16, 100}, {50, 101}}, {{25, 201}, {27, 202}},
+ {{25, 301}, {27, 302}});
+ ASSERT_FALSE(
+ transformation_bad_3.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableCFGTest3) {
+ // This test handles a case where for the block which is not the exit block,
+ // not all successors are in the region.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %14 "a"
+ OpName %19 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeBool
+ %9 = OpConstantTrue %8
+ %12 = OpTypeInt 32 1
+ %13 = OpTypePointer Function %12
+ %15 = OpConstant %12 2
+ %17 = OpConstant %12 3
+ %18 = OpTypePointer Function %8
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %19 = OpVariable %18 Function
+ OpStore %19 %9
+ %20 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %14 = OpVariable %13 Function
+ OpSelectionMerge %11 None
+ OpBranchConditional %9 %10 %16
+ %10 = OpLabel
+ OpStore %14 %15
+ OpBranch %11
+ %16 = OpLabel
+ OpStore %14 %17
+ OpBranch %11
+ %11 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+ // Bad: The block with id 7, which is not an exit block, has two successors:
+ // the block with id 10 and the block with id 16. The block with id 16 is not
+ // in the region.
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 30, 501, 7, 10, {{13, 100}}, {{14, 201}}, {{14, 301}});
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, MultipleBlocksLoopTest) {
+ // This test handles a case where the region consists of multiple blocks
+ // (they form a loop). The transformation is applicable and the region is
+ // duplicated.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %12 "i"
+ OpName %29 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %19 = OpConstant %8 10
+ %20 = OpTypeBool
+ %26 = OpConstant %8 1
+ %28 = OpTypePointer Function %20
+ %30 = OpConstantTrue %20
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %29 = OpVariable %28 Function
+ OpStore %29 %30
+ %31 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ %12 = OpVariable %9 Function
+ OpStore %10 %11
+ OpStore %12 %11
+ OpBranch %50
+ %50 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %15 %16 None
+ OpBranch %17
+ %17 = OpLabel
+ %18 = OpLoad %8 %12
+ %21 = OpSLessThan %20 %18 %19
+ OpBranchConditional %21 %14 %15
+ %14 = OpLabel
+ %22 = OpLoad %8 %10
+ %23 = OpLoad %8 %12
+ %24 = OpIAdd %8 %22 %23
+ OpStore %10 %24
+ OpBranch %16
+ %16 = OpLabel
+ %25 = OpLoad %8 %12
+ %27 = OpIAdd %8 %25 %26
+ OpStore %12 %27
+ OpBranch %13
+ %15 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 30, 501, 50, 15,
+ {{50, 100}, {13, 101}, {14, 102}, {15, 103}, {16, 104}, {17, 105}},
+ {{22, 201},
+ {23, 202},
+ {24, 203},
+ {25, 204},
+ {27, 205},
+ {18, 206},
+ {21, 207}},
+ {{22, 301},
+ {23, 302},
+ {24, 303},
+ {25, 304},
+ {27, 305},
+ {18, 306},
+ {21, 307}});
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %12 "i"
+ OpName %29 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %19 = OpConstant %8 10
+ %20 = OpTypeBool
+ %26 = OpConstant %8 1
+ %28 = OpTypePointer Function %20
+ %30 = OpConstantTrue %20
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %29 = OpVariable %28 Function
+ OpStore %29 %30
+ %31 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ %12 = OpVariable %9 Function
+ OpStore %10 %11
+ OpStore %12 %11
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %30 %50 %100
+ %50 = OpLabel
+ OpBranch %13
+ %13 = OpLabel
+ OpLoopMerge %15 %16 None
+ OpBranch %17
+ %17 = OpLabel
+ %18 = OpLoad %8 %12
+ %21 = OpSLessThan %20 %18 %19
+ OpBranchConditional %21 %14 %15
+ %14 = OpLabel
+ %22 = OpLoad %8 %10
+ %23 = OpLoad %8 %12
+ %24 = OpIAdd %8 %22 %23
+ OpStore %10 %24
+ OpBranch %16
+ %16 = OpLabel
+ %25 = OpLoad %8 %12
+ %27 = OpIAdd %8 %25 %26
+ OpStore %12 %27
+ OpBranch %13
+ %15 = OpLabel
+ OpBranch %501
+ %100 = OpLabel
+ OpBranch %101
+ %101 = OpLabel
+ OpLoopMerge %103 %104 None
+ OpBranch %105
+ %105 = OpLabel
+ %206 = OpLoad %8 %12
+ %207 = OpSLessThan %20 %206 %19
+ OpBranchConditional %207 %102 %103
+ %102 = OpLabel
+ %201 = OpLoad %8 %10
+ %202 = OpLoad %8 %12
+ %203 = OpIAdd %8 %201 %202
+ OpStore %10 %203
+ OpBranch %104
+ %104 = OpLabel
+ %204 = OpLoad %8 %12
+ %205 = OpIAdd %8 %204 %26
+ OpStore %12 %205
+ OpBranch %101
+ %103 = OpLabel
+ OpBranch %501
+ %501 = OpLabel
+ %306 = OpPhi %8 %18 %15 %206 %103
+ %307 = OpPhi %20 %21 %15 %207 %103
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+ ResolvingOpPhiExitBlockTest) {
+ // This test handles a case where the region under the transformation is
+ // referenced in OpPhi instructions. Since the new merge block becomes the
+ // exit of the region, these OpPhi instructions need to be updated.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %26 "b"
+ OpName %29 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %16 = OpTypeBool
+ %25 = OpTypePointer Function %16
+ %27 = OpConstantTrue %16
+ %28 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %26 = OpVariable %25 Function
+ %29 = OpVariable %7 Function
+ OpStore %26 %27
+ OpStore %29 %28
+ %30 = OpFunctionCall %2 %10 %29
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpStore %12 %13
+ %14 = OpLoad %6 %9
+ %17 = OpSLessThan %16 %14 %15
+ OpSelectionMerge %19 None
+ OpBranchConditional %17 %18 %22
+ %18 = OpLabel
+ %20 = OpLoad %6 %9
+ %21 = OpIAdd %6 %20 %15
+ OpStore %12 %21
+ OpBranch %19
+ %22 = OpLabel
+ %23 = OpLoad %6 %9
+ %24 = OpIMul %6 %23 %15
+ OpStore %12 %24
+ OpBranch %19
+ %19 = OpLabel
+ %40 = OpPhi %6 %21 %18 %24 %22
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 27, 501, 22, 22, {{22, 100}}, {{23, 201}, {24, 202}},
+ {{23, 301}, {24, 302}});
+
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %26 "b"
+ OpName %29 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %16 = OpTypeBool
+ %25 = OpTypePointer Function %16
+ %27 = OpConstantTrue %16
+ %28 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %26 = OpVariable %25 Function
+ %29 = OpVariable %7 Function
+ OpStore %26 %27
+ OpStore %29 %28
+ %30 = OpFunctionCall %2 %10 %29
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpStore %12 %13
+ %14 = OpLoad %6 %9
+ %17 = OpSLessThan %16 %14 %15
+ OpSelectionMerge %19 None
+ OpBranchConditional %17 %18 %500
+ %18 = OpLabel
+ %20 = OpLoad %6 %9
+ %21 = OpIAdd %6 %20 %15
+ OpStore %12 %21
+ OpBranch %19
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %27 %22 %100
+ %22 = OpLabel
+ %23 = OpLoad %6 %9
+ %24 = OpIMul %6 %23 %15
+ OpStore %12 %24
+ OpBranch %501
+ %100 = OpLabel
+ %201 = OpLoad %6 %9
+ %202 = OpIMul %6 %201 %15
+ OpStore %12 %202
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %6 %23 %22 %201 %100
+ %302 = OpPhi %6 %24 %22 %202 %100
+ OpBranch %19
+ %19 = OpLabel
+ %40 = OpPhi %6 %21 %18 %302 %501
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest, NotApplicableEarlyReturn) {
+ // This test handles a case where one of the blocks has successor outside of
+ // the region, which has an early return from the function, so that the
+ // transformation is not applicable.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %27 "b"
+ OpName %30 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %16 = OpTypeBool
+ %26 = OpTypePointer Function %16
+ %28 = OpConstantTrue %16
+ %29 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %27 = OpVariable %26 Function
+ %30 = OpVariable %7 Function
+ OpStore %27 %28
+ OpStore %30 %29
+ %31 = OpFunctionCall %2 %10 %30
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ OpBranch %50
+ %50 = OpLabel
+ OpStore %12 %13
+ %14 = OpLoad %6 %9
+ %17 = OpSLessThan %16 %14 %15
+ OpSelectionMerge %19 None
+ OpBranchConditional %17 %18 %22
+ %18 = OpLabel
+ %20 = OpLoad %6 %9
+ %21 = OpIAdd %6 %20 %15
+ OpStore %12 %21
+ OpBranch %19
+ %22 = OpLabel
+ OpReturn
+ %19 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Bad: The block with id 50, which is the entry block, has two successors:
+ // the block with id 18 and the block with id 22. The block 22 has an early
+ // return from the function, so that the entry block is not post-dominated by
+ // the exit block.
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 28, 501, 50, 19, {{50, 100}, {18, 101}, {22, 102}, {19, 103}},
+ {{14, 202}, {17, 203}, {20, 204}, {21, 205}},
+ {{14, 302}, {17, 303}, {20, 304}, {21, 305}});
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+ ResolvingOpPhiEntryBlockOnePredecessor) {
+ // This test handles a case where the entry block has an OpPhi instruction
+ // referring to its predecessor. After transformation, this instruction needs
+ // to be updated.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %14 "t"
+ OpName %20 "b"
+ OpName %23 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %18 = OpTypeBool
+ %19 = OpTypePointer Function %18
+ %21 = OpConstantTrue %18
+ %22 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpVariable %19 Function
+ %23 = OpVariable %7 Function
+ OpStore %20 %21
+ OpStore %23 %22
+ %24 = OpFunctionCall %2 %10 %23
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ %14 = OpVariable %7 Function
+ OpStore %12 %13
+ %16 = OpLoad %6 %12
+ %17 = OpIMul %6 %15 %16
+ OpStore %14 %17
+ OpBranch %50
+ %50 = OpLabel
+ %51 = OpPhi %6 %17 %11
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}});
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %14 "t"
+ OpName %20 "b"
+ OpName %23 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %18 = OpTypeBool
+ %19 = OpTypePointer Function %18
+ %21 = OpConstantTrue %18
+ %22 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpVariable %19 Function
+ %23 = OpVariable %7 Function
+ OpStore %20 %21
+ OpStore %23 %22
+ %24 = OpFunctionCall %2 %10 %23
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ %14 = OpVariable %7 Function
+ OpStore %12 %13
+ %16 = OpLoad %6 %12
+ %17 = OpIMul %6 %15 %16
+ OpStore %14 %17
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %21 %50 %100
+ %50 = OpLabel
+ %51 = OpPhi %6 %17 %500
+ OpBranch %501
+ %100 = OpLabel
+ %201 = OpPhi %6 %17 %500
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %6 %51 %50 %201 %100
+ OpReturn
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+ NotApplicableNoVariablePointerCapability) {
+ // This test handles a case where the transformation would create an OpPhi
+ // instruction with pointer operands, however there is no cab
+ // CapabilityVariablePointers. Hence, the transformation is not applicable.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %10 "fun(i1;"
+ OpName %9 "a"
+ OpName %12 "s"
+ OpName %14 "t"
+ OpName %20 "b"
+ OpName %23 "param"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 1
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeFunction %2 %7
+ %13 = OpConstant %6 0
+ %15 = OpConstant %6 2
+ %18 = OpTypeBool
+ %19 = OpTypePointer Function %18
+ %21 = OpConstantTrue %18
+ %22 = OpConstant %6 3
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %20 = OpVariable %19 Function
+ %23 = OpVariable %7 Function
+ OpStore %20 %21
+ OpStore %23 %22
+ %24 = OpFunctionCall %2 %10 %23
+ OpReturn
+ OpFunctionEnd
+ %10 = OpFunction %2 None %8
+ %9 = OpFunctionParameter %7
+ %11 = OpLabel
+ %12 = OpVariable %7 Function
+ %14 = OpVariable %7 Function
+ OpStore %12 %13
+ %16 = OpLoad %6 %12
+ %17 = OpIMul %6 %15 %16
+ OpStore %14 %17
+ OpBranch %50
+ %50 = OpLabel
+ %51 = OpCopyObject %7 %12
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Bad: There is no required capability CapabilityVariablePointers
+ TransformationDuplicateRegionWithSelection transformation_bad_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 21, 501, 50, 50, {{50, 100}}, {{51, 201}}, {{51, 301}});
+ ASSERT_FALSE(
+ transformation_bad_1.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+ ExitBlockTerminatorOpUnreachable) {
+ // This test handles a case where the exit block ends with OpUnreachable.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %17 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %13 = OpConstant %8 2
+ %15 = OpTypeBool
+ %16 = OpTypePointer Function %15
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %17 = OpVariable %16 Function
+ OpStore %17 %18
+ %19 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ OpBranch %50
+ %50 = OpLabel
+ OpStore %10 %11
+ %12 = OpLoad %8 %10
+ %14 = OpIAdd %8 %12 %13
+ OpStore %10 %14
+ OpUnreachable
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}},
+ {{12, 301}, {14, 302}});
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %17 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %13 = OpConstant %8 2
+ %15 = OpTypeBool
+ %16 = OpTypePointer Function %15
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %17 = OpVariable %16 Function
+ OpStore %17 %18
+ %19 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %18 %50 %100
+ %50 = OpLabel
+ OpStore %10 %11
+ %12 = OpLoad %8 %10
+ %14 = OpIAdd %8 %12 %13
+ OpStore %10 %14
+ OpBranch %501
+ %100 = OpLabel
+ OpStore %10 %11
+ %201 = OpLoad %8 %10
+ %202 = OpIAdd %8 %201 %13
+ OpStore %10 %202
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %8 %12 %50 %201 %100
+ %302 = OpPhi %8 %14 %50 %202 %100
+ OpUnreachable
+ OpFunctionEnd
+ )";
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationDuplicateRegionWithSelectionTest,
+ ExitBlockTerminatorOpKill) {
+ // This test handles a case where the exit block ends with OpKill.
+
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %17 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %13 = OpConstant %8 2
+ %15 = OpTypeBool
+ %16 = OpTypePointer Function %15
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %17 = OpVariable %16 Function
+ OpStore %17 %18
+ %19 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ OpBranch %50
+ %50 = OpLabel
+ OpStore %10 %11
+ %12 = OpLoad %8 %10
+ %14 = OpIAdd %8 %12 %13
+ OpStore %10 %14
+ OpKill
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_4;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ TransformationDuplicateRegionWithSelection transformation_good_1 =
+ TransformationDuplicateRegionWithSelection(
+ 500, 18, 501, 50, 50, {{50, 100}}, {{12, 201}, {14, 202}},
+ {{12, 301}, {14, 302}});
+ ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+ transformation_context));
+ transformation_good_1.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string expected_shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main"
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %6 "fun("
+ OpName %10 "s"
+ OpName %17 "b"
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %8 = OpTypeInt 32 1
+ %9 = OpTypePointer Function %8
+ %11 = OpConstant %8 0
+ %13 = OpConstant %8 2
+ %15 = OpTypeBool
+ %16 = OpTypePointer Function %15
+ %18 = OpConstantTrue %15
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %17 = OpVariable %16 Function
+ OpStore %17 %18
+ %19 = OpFunctionCall %2 %6
+ OpReturn
+ OpFunctionEnd
+ %6 = OpFunction %2 None %3
+ %7 = OpLabel
+ %10 = OpVariable %9 Function
+ OpBranch %500
+ %500 = OpLabel
+ OpSelectionMerge %501 None
+ OpBranchConditional %18 %50 %100
+ %50 = OpLabel
+ OpStore %10 %11
+ %12 = OpLoad %8 %10
+ %14 = OpIAdd %8 %12 %13
+ OpStore %10 %14
+ OpBranch %501
+ %100 = OpLabel
+ OpStore %10 %11
+ %201 = OpLoad %8 %10
+ %202 = OpIAdd %8 %201 %13
+ OpStore %10 %202
+ OpBranch %501
+ %501 = OpLabel
+ %301 = OpPhi %8 %12 %50 %201 %100
+ %302 = OpPhi %8 %14 %50 %202 %100
+ OpKill
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools