diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt index 4d9ecddcdf..e1cc9258b8 100644 --- a/source/fuzz/CMakeLists.txt +++ b/source/fuzz/CMakeLists.txt @@ -80,6 +80,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.h fuzzer_pass_copy_objects.h fuzzer_pass_donate_modules.h + fuzzer_pass_duplicate_regions_with_selections.h fuzzer_pass_inline_functions.h fuzzer_pass_invert_comparison_operators.h fuzzer_pass_interchange_signedness_of_integer_operands.h @@ -156,6 +157,7 @@ if(SPIRV_BUILD_FUZZER) transformation_composite_insert.h transformation_compute_data_synonym_fact_closure.h transformation_context.h + transformation_duplicate_region_with_selection.h transformation_equation_instruction.h transformation_function_call.h transformation_inline_function.h @@ -241,6 +243,7 @@ if(SPIRV_BUILD_FUZZER) fuzzer_pass_construct_composites.cpp fuzzer_pass_copy_objects.cpp fuzzer_pass_donate_modules.cpp + fuzzer_pass_duplicate_regions_with_selections.cpp fuzzer_pass_inline_functions.cpp fuzzer_pass_invert_comparison_operators.cpp fuzzer_pass_interchange_signedness_of_integer_operands.cpp @@ -316,7 +319,7 @@ if(SPIRV_BUILD_FUZZER) transformation_composite_insert.cpp transformation_compute_data_synonym_fact_closure.cpp transformation_context.cpp - transformation_replace_opselect_with_conditional_branch.cpp + transformation_duplicate_region_with_selection.cpp transformation_equation_instruction.cpp transformation_function_call.cpp transformation_inline_function.cpp @@ -343,6 +346,7 @@ if(SPIRV_BUILD_FUZZER) transformation_replace_linear_algebra_instruction.cpp transformation_replace_load_store_with_copy_memory.cpp transformation_replace_opphi_id_from_dead_predecessor.cpp + transformation_replace_opselect_with_conditional_branch.cpp transformation_replace_parameter_with_global.cpp transformation_replace_params_with_struct.cpp transformation_set_function_control.cpp diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp index ee638b6fe5..8b9c9cbdcb 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 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( MaybeAddPass( &passes, ir_context.get(), &transformation_context, &fuzzer_context, transformation_sequence_out, donor_suppliers); + MaybeAddPass( + &passes, ir_context.get(), &transformation_context, &fuzzer_context, + transformation_sequence_out); MaybeAddPass( &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 3443a14890..869cb5a9e5 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -70,6 +70,8 @@ const std::pair kChanceOfChoosingWorkgroupStorageClass = { const std::pair kChanceOfConstructingComposite = {20, 50}; const std::pair kChanceOfCopyingObject = {20, 50}; const std::pair kChanceOfDonatingAdditionalModule = {5, 50}; +const std::pair kChanceOfDuplicatingRegionWithSelection = { + 20, 50}; const std::pair kChanceOfGoingDeeperToInsertInComposite = { 30, 70}; const std::pair kChanceOfGoingDeeperWhenMakingAccessChain = @@ -230,6 +232,8 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, chance_of_copying_object_ = ChooseBetweenMinAndMax(kChanceOfCopyingObject); chance_of_donating_additional_module_ = ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule); + chance_of_duplicating_region_with_selection_ = + ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection); chance_of_going_deeper_to_insert_in_composite_ = ChooseBetweenMinAndMax(kChanceOfGoingDeeperToInsertInComposite); chance_of_going_deeper_when_making_access_chain_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index 7427a4189f..8f045c377f 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -198,6 +198,9 @@ class FuzzerContext { uint32_t GetChanceOfDonatingAdditionalModule() { return chance_of_donating_additional_module_; } + uint32_t GetChanceOfDuplicatingRegionWithSelection() { + return chance_of_duplicating_region_with_selection_; + } uint32_t GetChanceOfGoingDeeperToInsertInComposite() { return chance_of_going_deeper_to_insert_in_composite_; } @@ -406,6 +409,7 @@ class FuzzerContext { uint32_t chance_of_constructing_composite_; uint32_t chance_of_copying_object_; uint32_t chance_of_donating_additional_module_; + uint32_t chance_of_duplicating_region_with_selection_; uint32_t chance_of_going_deeper_to_insert_in_composite_; uint32_t chance_of_going_deeper_when_making_access_chain_; uint32_t chance_of_inlining_function_; 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 0000000000..45b51ec976 --- /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 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 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 original_label_to_duplicate_label; + std::map original_id_to_duplicate_id; + std::map 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 0000000000..3fae69836a --- /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 5d27761f85..f1fa6ac277 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -419,6 +419,7 @@ message Transformation { TransformationReplaceIrrelevantId replace_irrelevant_id = 72; TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73; TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74; + TransformationDuplicateRegionWithSelection duplicate_region_with_selection = 75; // Add additional option using the next available number. } } @@ -1082,6 +1083,41 @@ message TransformationComputeDataSynonymFactClosure { } +message TransformationDuplicateRegionWithSelection { + + // A transformation that inserts a conditional statement with a boolean expression + // of arbitrary value and duplicates a given single-entry, single-exit region, so + // that it is present in each conditional branch and will be executed regardless + // of which branch will be taken. + + // Fresh id for a label of the new entry block. + uint32 new_entry_fresh_id = 1; + + // Id for a boolean expression. + uint32 condition_id = 2; + + // Fresh id for a label of the merge block of the conditional. + uint32 merge_label_fresh_id = 3; + + // Block id of the entry block of the original region. + uint32 entry_block_id = 4; + + // Block id of the exit block of the original region. + uint32 exit_block_id = 5; + + // Map that maps from a label in the original region to the corresponding label + // in the duplicated region. + repeated UInt32Pair original_label_to_duplicate_label = 6; + + // Map that maps from a result id in the original region to the corresponding + // result id in the duplicated region. + repeated UInt32Pair original_id_to_duplicate_id = 7; + + // Map that maps from a result id in the original region to the result id of the + // corresponding OpPhi instruction. + repeated UInt32Pair original_id_to_phi_id = 8; +} + message TransformationEquationInstruction { // A transformation that adds an instruction to the module that defines an diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp index 7156f6e082..2f00dc3841 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 @@ std::unique_ptr Transformation::FromMessage( kComputeDataSynonymFactClosure: return MakeUnique( message.compute_data_synonym_fact_closure()); + case protobufs::Transformation::TransformationCase:: + kDuplicateRegionWithSelection: + return MakeUnique( + message.duplicate_region_with_selection()); case protobufs::Transformation::TransformationCase::kEquationInstruction: return MakeUnique( 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 0000000000..3b6c1df81a --- /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& original_label_to_duplicate_label, + const std::map& original_id_to_duplicate_id, + const std::map& 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 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 original_label_to_duplicate_label = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_label_to_duplicate_label()); + + std::map original_id_to_duplicate_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_id_to_duplicate_id()); + + std::map 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 new_entry_block = + MakeUnique(MakeUnique( + 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 region_blocks = + GetRegionBlocks(ir_context, entry_block, exit_block); + + // Construct the merge block. + std::unique_ptr merge_block = + MakeUnique(MakeUnique(opt::Instruction( + ir_context, SpvOpLabel, 0, message_.merge_label_fresh_id(), + opt::Instruction::OperandList()))); + + // Get the maps from the protobuf. + std::map original_label_to_duplicate_label = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_label_to_duplicate_label()); + + std::map original_id_to_duplicate_id = + fuzzerutil::RepeatedUInt32PairToMap( + message_.original_id_to_duplicate_id()); + + std::map 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 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 duplicated_block = + MakeUnique(MakeUnique( + 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(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( + 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( + 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( + 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(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(merge_branch_instr)); + duplicated_exit_block->AddInstruction( + MakeUnique(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 +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 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 0000000000..a1a2a8936e --- /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& original_label_to_duplicate_label, + const std::map& original_id_to_duplicate_id, + const std::map& 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 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 6199e6902a..f4add4cc65 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -62,6 +62,7 @@ if (${SPIRV_BUILD_FUZZER}) transformation_composite_extract_test.cpp transformation_composite_insert_test.cpp transformation_compute_data_synonym_fact_closure_test.cpp + transformation_duplicate_region_with_selection_test.cpp transformation_equation_instruction_test.cpp transformation_function_call_test.cpp transformation_inline_function_test.cpp 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 0000000000..6539c4a37e --- /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