spirv-fuzz: Transformation to convert OpSelect to conditional branch (#3681)
This transformation takes an OpSelect instruction and replaces it with
a conditional branch, selecting the correct value using an OpPhi
instruction.
Fixes part of the issue #3544.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 288ea25..523f578 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -95,6 +95,7 @@
fuzzer_pass_replace_linear_algebra_instructions.h
fuzzer_pass_replace_loads_stores_with_copy_memories.h
fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
+ fuzzer_pass_replace_opselects_with_conditional_branches.h
fuzzer_pass_replace_parameter_with_global.h
fuzzer_pass_replace_params_with_struct.h
fuzzer_pass_split_blocks.h
@@ -174,6 +175,7 @@
transformation_replace_linear_algebra_instruction.h
transformation_replace_load_store_with_copy_memory.h
transformation_replace_opphi_id_from_dead_predecessor.h
+ transformation_replace_opselect_with_conditional_branch.h
transformation_replace_parameter_with_global.h
transformation_replace_params_with_struct.h
transformation_set_function_control.h
@@ -254,6 +256,7 @@
fuzzer_pass_replace_linear_algebra_instructions.cpp
fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
+ fuzzer_pass_replace_opselects_with_conditional_branches.cpp
fuzzer_pass_replace_parameter_with_global.cpp
fuzzer_pass_replace_params_with_struct.cpp
fuzzer_pass_split_blocks.cpp
@@ -306,6 +309,7 @@
transformation_composite_insert.cpp
transformation_compute_data_synonym_fact_closure.cpp
transformation_context.cpp
+ transformation_replace_opselect_with_conditional_branch.cpp
transformation_equation_instruction.cpp
transformation_function_call.cpp
transformation_inline_function.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 3656e92..ee638b6 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -71,6 +71,7 @@
#include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
#include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
+#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
#include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
#include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
@@ -325,6 +326,9 @@
MaybeAddPass<FuzzerPassReplaceLinearAlgebraInstructions>(
&passes, ir_context.get(), &transformation_context, &fuzzer_context,
transformation_sequence_out);
+ MaybeAddPass<FuzzerPassReplaceOpSelectsWithConditionalBranches>(
+ &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassReplaceParamsWithStruct>(
&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 153b9ef..3443a14 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -27,6 +27,8 @@
const std::pair<uint32_t, uint32_t> kChanceOfAddingAnotherStructField = {20,
90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingArrayOrStructType = {20, 90};
+const std::pair<uint32_t, uint32_t>
+ kChanceOfAddingBothBranchesWhenReplacingOpSelect = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCompositeInsert = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingCopyMemory = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingDeadBlock = {20, 90};
@@ -48,6 +50,8 @@
const std::pair<uint32_t, uint32_t> kChanceOfAddingRelaxedDecoration = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
const std::pair<uint32_t, uint32_t> kChanceOfAddingSynonyms = {20, 50};
+const std::pair<uint32_t, uint32_t>
+ kChanceOfAddingTrueBranchWhenReplacingOpSelect = {40, 60};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70};
const std::pair<uint32_t, uint32_t> kChanceOfAdjustingBranchWeights = {20, 90};
@@ -105,6 +109,8 @@
{20, 90};
const std::pair<uint32_t, uint32_t>
kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90};
+const std::pair<uint32_t, uint32_t>
+ kChanceOfReplacingOpSelectWithConditionalBranch = {20, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
30, 70};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
@@ -162,6 +168,8 @@
ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField);
chance_of_adding_array_or_struct_type_ =
ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType);
+ chance_of_adding_both_branches_when_replacing_opselect_ =
+ ChooseBetweenMinAndMax(kChanceOfAddingBothBranchesWhenReplacingOpSelect);
chance_of_adding_composite_insert_ =
ChooseBetweenMinAndMax(kChanceOfAddingCompositeInsert);
chance_of_adding_copy_memory_ =
@@ -194,6 +202,8 @@
chance_of_adding_relaxed_decoration_ =
ChooseBetweenMinAndMax(kChanceOfAddingRelaxedDecoration);
chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
+ chance_of_adding_true_branch_when_replacing_opselect_ =
+ ChooseBetweenMinAndMax(kChanceOfAddingTrueBranchWhenReplacingOpSelect);
chance_of_adding_vector_shuffle_ =
ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle);
chance_of_adding_vector_type_ =
@@ -271,6 +281,8 @@
ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory);
chance_of_replacing_opphi_id_from_dead_predecessor_ =
ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor);
+ chance_of_replacing_opselect_with_conditional_branch_ =
+ ChooseBetweenMinAndMax(kChanceOfReplacingOpSelectWithConditionalBranch);
chance_of_replacing_parameters_with_globals_ =
ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
chance_of_replacing_parameters_with_struct_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 594cb90..7427a41 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -115,6 +115,9 @@
uint32_t GetChanceOfAddingArrayOrStructType() {
return chance_of_adding_array_or_struct_type_;
}
+ uint32_t GetChanceOfAddingBothBranchesWhenReplacingOpSelect() {
+ return chance_of_adding_both_branches_when_replacing_opselect_;
+ }
uint32_t GetChanceOfAddingCompositeInsert() {
return chance_of_adding_composite_insert_;
}
@@ -157,6 +160,9 @@
}
uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
uint32_t GetChanceOfAddingSynonyms() { return chance_of_adding_synonyms_; }
+ uint32_t GetChanceOfAddingTrueBranchWhenReplacingOpSelect() {
+ return chance_of_adding_true_branch_when_replacing_opselect_;
+ }
uint32_t GetChanceOfAddingVectorShuffle() {
return chance_of_adding_vector_shuffle_;
}
@@ -264,6 +270,9 @@
uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() {
return chance_of_replacing_opphi_id_from_dead_predecessor_;
}
+ uint32_t GetChanceOfReplacingOpselectWithConditionalBranch() {
+ return chance_of_replacing_opselect_with_conditional_branch_;
+ }
uint32_t GetChanceOfReplacingParametersWithGlobals() {
return chance_of_replacing_parameters_with_globals_;
}
@@ -364,6 +373,7 @@
uint32_t chance_of_adding_access_chain_;
uint32_t chance_of_adding_another_struct_field_;
uint32_t chance_of_adding_array_or_struct_type_;
+ uint32_t chance_of_adding_both_branches_when_replacing_opselect_;
uint32_t chance_of_adding_composite_insert_;
uint32_t chance_of_adding_copy_memory_;
uint32_t chance_of_adding_dead_block_;
@@ -382,6 +392,7 @@
uint32_t chance_of_adding_relaxed_decoration_;
uint32_t chance_of_adding_store_;
uint32_t chance_of_adding_synonyms_;
+ uint32_t chance_of_adding_true_branch_when_replacing_opselect_;
uint32_t chance_of_adding_vector_shuffle_;
uint32_t chance_of_adding_vector_type_;
uint32_t chance_of_adjusting_branch_weights_;
@@ -421,6 +432,7 @@
uint32_t chance_of_replacing_linear_algebra_instructions_;
uint32_t chance_of_replacing_load_store_with_copy_memory_;
uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_;
+ uint32_t chance_of_replacing_opselect_with_conditional_branch_;
uint32_t chance_of_replacing_parameters_with_globals_;
uint32_t chance_of_replacing_parameters_with_struct_;
uint32_t chance_of_splitting_block_;
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
new file mode 100644
index 0000000..0496268
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.cpp
@@ -0,0 +1,162 @@
+// 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_replace_opselects_with_conditional_branches.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
+#include "source/fuzz/transformation_split_block.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceOpSelectsWithConditionalBranches::
+ FuzzerPassReplaceOpSelectsWithConditionalBranches(
+ opt::IRContext* ir_context,
+ TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+ transformations) {}
+
+FuzzerPassReplaceOpSelectsWithConditionalBranches::
+ ~FuzzerPassReplaceOpSelectsWithConditionalBranches() = default;
+
+void FuzzerPassReplaceOpSelectsWithConditionalBranches::Apply() {
+ // Keep track of the instructions that we want to replace. We need to collect
+ // them in a vector, since it's not safe to modify the module while iterating
+ // over it.
+ std::vector<uint32_t> replaceable_opselect_instruction_ids;
+
+ // Loop over all the instructions in the module.
+ for (auto& function : *GetIRContext()->module()) {
+ for (auto& block : function) {
+ // We cannot split loop headers, so we don't need to consider instructions
+ // in loop headers that are also merge blocks (since they would need to be
+ // split).
+ if (block.IsLoopHeader() &&
+ GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(
+ block.id())) {
+ continue;
+ }
+
+ for (auto& instruction : block) {
+ // We only care about OpSelect instructions.
+ if (instruction.opcode() != SpvOpSelect) {
+ continue;
+ }
+
+ // Randomly choose whether to consider this instruction for replacement.
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()
+ ->GetChanceOfReplacingOpselectWithConditionalBranch())) {
+ continue;
+ }
+
+ // If the block is a loop header and we need to split it, the
+ // transformation cannot be applied because loop headers cannot be
+ // split. We can break out of this loop because the transformation can
+ // only be applied to at most the first instruction in a loop header.
+ if (block.IsLoopHeader() && InstructionNeedsSplitBefore(&instruction)) {
+ break;
+ }
+
+ // If the instruction separates an OpSampledImage from its use, the
+ // block cannot be split around it and the instruction cannot be
+ // replaced.
+ if (fuzzerutil::
+ SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+ &block, &instruction)) {
+ continue;
+ }
+
+ // We can apply the transformation to this instruction.
+ replaceable_opselect_instruction_ids.push_back(instruction.result_id());
+ }
+ }
+ }
+
+ // Apply the transformations, splitting the blocks containing the
+ // instructions, if necessary.
+ for (uint32_t instruction_id : replaceable_opselect_instruction_ids) {
+ auto instruction =
+ GetIRContext()->get_def_use_mgr()->GetDef(instruction_id);
+
+ // If the instruction requires the block containing it to be split before
+ // it, split the block.
+ if (InstructionNeedsSplitBefore(instruction)) {
+ ApplyTransformation(TransformationSplitBlock(
+ MakeInstructionDescriptor(GetIRContext(), instruction),
+ GetFuzzerContext()->GetFreshId()));
+ }
+
+ // Decide whether to have two branches or just one.
+ bool two_branches = GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()
+ ->GetChanceOfAddingBothBranchesWhenReplacingOpSelect());
+
+ // If there will be only one branch, decide whether it will be the true
+ // branch or the false branch.
+ bool true_branch_id_zero =
+ !two_branches &&
+ GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()
+ ->GetChanceOfAddingTrueBranchWhenReplacingOpSelect());
+ bool false_branch_id_zero = !two_branches && !true_branch_id_zero;
+
+ uint32_t true_branch_id =
+ true_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
+ uint32_t false_branch_id =
+ false_branch_id_zero ? 0 : GetFuzzerContext()->GetFreshId();
+
+ ApplyTransformation(TransformationReplaceOpSelectWithConditionalBranch(
+ instruction_id, true_branch_id, false_branch_id));
+ }
+}
+
+bool FuzzerPassReplaceOpSelectsWithConditionalBranches::
+ InstructionNeedsSplitBefore(opt::Instruction* instruction) {
+ assert(instruction && instruction->opcode() == SpvOpSelect &&
+ "The instruction must be OpSelect.");
+
+ auto block = GetIRContext()->get_instr_block(instruction);
+ assert(block && "The instruction must be contained in a block.");
+
+ // We need to split the block if the instruction is not the first in its
+ // block.
+ if (instruction->unique_id() != block->begin()->unique_id()) {
+ return true;
+ }
+
+ // We need to split the block if it is a merge block.
+ if (GetIRContext()->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+ return true;
+ }
+
+ // We need to split the block if it has more than one predecessor.
+ if (GetIRContext()->cfg()->preds(block->id()).size() != 1) {
+ return true;
+ }
+
+ // We need to split the block if its predecessor is a header or it does not
+ // branch unconditionally to the block.
+ auto predecessor = GetIRContext()->get_instr_block(
+ GetIRContext()->cfg()->preds(block->id())[0]);
+ return predecessor->MergeBlockIdIfAny() ||
+ predecessor->terminator()->opcode() != SpvOpBranch;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
new file mode 100644
index 0000000..ef3ec57
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h
@@ -0,0 +1,53 @@
+// 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_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass to replace OpSelect instructions (where the condition is a
+// scalar boolean) with conditional branches and OpPhi instructions.
+class FuzzerPassReplaceOpSelectsWithConditionalBranches : public FuzzerPass {
+ public:
+ FuzzerPassReplaceOpSelectsWithConditionalBranches(
+ opt::IRContext* ir_context, TransformationContext* transformation_context,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassReplaceOpSelectsWithConditionalBranches() override;
+
+ void Apply() override;
+
+ private:
+ // Returns true if any of the following holds:
+ // - the instruction is not the first in its block
+ // - the block containing it is a merge block
+ // - the block does not have a unique predecessor
+ // - the predecessor of the block is the header of a construct
+ // - the predecessor does not branch unconditionally to the block
+ // If this function returns true, the block must be split before the
+ // instruction for TransformationReplaceOpSelectWithConditionalBranch to be
+ // applicable.
+ // Assumes that the instruction is OpSelect.
+ bool InstructionNeedsSplitBefore(opt::Instruction* instruction);
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPSELECTS_WITH_CONDITIONAL_BRANCHES_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index aa45d66..c6d18c0 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -1486,6 +1486,39 @@
return builtin_count != 0;
}
+bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+ opt::BasicBlock* block_to_split, opt::Instruction* split_before) {
+ std::set<uint32_t> sampled_image_result_ids;
+ bool before_split = true;
+
+ // Check all the instructions in the block to split.
+ for (auto& instruction : *block_to_split) {
+ if (&instruction == &*split_before) {
+ before_split = false;
+ }
+ if (before_split) {
+ // If the instruction comes before the split and its opcode is
+ // OpSampledImage, record its result id.
+ if (instruction.opcode() == SpvOpSampledImage) {
+ sampled_image_result_ids.insert(instruction.result_id());
+ }
+ } else {
+ // If the instruction comes after the split, check if ids
+ // corresponding to OpSampledImage instructions defined before the split
+ // are used, and return true if they are.
+ if (!instruction.WhileEachInId(
+ [&sampled_image_result_ids](uint32_t* id) -> bool {
+ return !sampled_image_result_ids.count(*id);
+ })) {
+ return true;
+ }
+ }
+ }
+
+ // No usage that would be separated from the definition has been found.
+ return false;
+}
+
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 865c1a0..2496c11 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -524,6 +524,11 @@
bool MembersHaveBuiltInDecoration(opt::IRContext* ir_context,
uint32_t struct_type_id);
+// Returns true iff splitting block |block_to_split| just before the instruction
+// |split_before| would separate an OpSampledImage instruction from its usage.
+bool SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+ opt::BasicBlock* block_to_split, opt::Instruction* split_before);
+
} // namespace fuzzerutil
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 0cc7a78..5d27761 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -418,6 +418,7 @@
TransformationMutatePointer mutate_pointer = 71;
TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
+ TransformationReplaceOpSelectWithConditionalBranch replace_opselect_with_conditional_branch = 74;
// Add additional option using the next available number.
}
}
@@ -1564,6 +1565,39 @@
}
+message TransformationReplaceOpSelectWithConditionalBranch {
+
+ // A transformation that takes an OpSelect instruction with a
+ // scalar boolean condition and replaces it with a conditional
+ // branch and an OpPhi instruction.
+ // The OpSelect instruction must be the first instruction in its
+ // block, which must have a unique predecessor. The block will
+ // become the merge block of a new construct, while its predecessor
+ // will become the header.
+ // Given the original OpSelect instruction:
+ // %id = OpSelect %type %cond %then %else
+ // The branching instruction of the header will be:
+ // OpBranchConditional %cond %true_block_id %false_block_id
+ // and the OpSelect instruction will be turned into:
+ // %id = OpPhi %type %then %true_block_id %else %false_block_id
+ // At most one of |true_block_id| and |false_block_id| can be zero. In
+ // that case, there will be no such block and all references to it
+ // will be replaced by %merge_block (where %merge_block is the
+ // block containing the OpSelect instruction).
+
+ // The result id of the OpSelect instruction.
+ uint32 select_id = 1;
+
+ // A fresh id for the new block that the predecessor of the block
+ // containing |select_id| will branch to if the condition holds.
+ uint32 true_block_id = 2;
+
+ // A fresh id for the new block that the predecessor of the block
+ // containing |select_id| will branch to if the condition does not
+ // hold.
+ uint32 false_block_id = 3;
+}
+
message TransformationReplaceParamsWithStruct {
// Replaces parameters of the function with a struct containing
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 2360901..7156f6e 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -78,6 +78,7 @@
#include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
#include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+#include "source/fuzz/transformation_replace_opselect_with_conditional_branch.h"
#include "source/fuzz/transformation_replace_parameter_with_global.h"
#include "source/fuzz/transformation_replace_params_with_struct.h"
#include "source/fuzz/transformation_set_function_control.h"
@@ -277,6 +278,10 @@
return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>(
message.replace_load_store_with_copy_memory());
case protobufs::Transformation::TransformationCase::
+ kReplaceOpselectWithConditionalBranch:
+ return MakeUnique<TransformationReplaceOpSelectWithConditionalBranch>(
+ message.replace_opselect_with_conditional_branch());
+ case protobufs::Transformation::TransformationCase::
kReplaceParameterWithGlobal:
return MakeUnique<TransformationReplaceParameterWithGlobal>(
message.replace_parameter_with_global());
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
new file mode 100644
index 0000000..5ae56fd
--- /dev/null
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.cpp
@@ -0,0 +1,204 @@
+// 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_replace_opselect_with_conditional_branch.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+TransformationReplaceOpSelectWithConditionalBranch::
+ TransformationReplaceOpSelectWithConditionalBranch(
+ const spvtools::fuzz::protobufs::
+ TransformationReplaceOpSelectWithConditionalBranch& message)
+ : message_(message) {}
+
+TransformationReplaceOpSelectWithConditionalBranch::
+ TransformationReplaceOpSelectWithConditionalBranch(
+ uint32_t select_id, uint32_t true_block_id, uint32_t false_block_id) {
+ message_.set_select_id(select_id);
+ message_.set_true_block_id(true_block_id);
+ message_.set_false_block_id(false_block_id);
+}
+
+bool TransformationReplaceOpSelectWithConditionalBranch::IsApplicable(
+ opt::IRContext* ir_context,
+ const TransformationContext& /* unused */) const {
+ assert((message_.true_block_id() || message_.false_block_id()) &&
+ "At least one of the ids must be non-zero.");
+
+ // Check that the non-zero ids are fresh.
+ std::set<uint32_t> used_ids;
+ for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
+ if (id && !CheckIdIsFreshAndNotUsedByThisTransformation(id, ir_context,
+ &used_ids)) {
+ return false;
+ }
+ }
+
+ auto instruction =
+ ir_context->get_def_use_mgr()->GetDef(message_.select_id());
+
+ // The instruction must exist and it must be an OpSelect instruction.
+ if (!instruction || instruction->opcode() != SpvOpSelect) {
+ return false;
+ }
+
+ // Check that the condition is a scalar boolean.
+ auto condition = ir_context->get_def_use_mgr()->GetDef(
+ instruction->GetSingleWordInOperand(0));
+ assert(condition && "The condition should always exist in a valid module.");
+
+ auto condition_type =
+ ir_context->get_type_mgr()->GetType(condition->type_id());
+ if (!condition_type->AsBool()) {
+ return false;
+ }
+
+ auto block = ir_context->get_instr_block(instruction);
+ assert(block && "The block containing the instruction must be found");
+
+ // The instruction must be the first in its block.
+ if (instruction->unique_id() != block->begin()->unique_id()) {
+ return false;
+ }
+
+ // The block must not be a merge block.
+ if (ir_context->GetStructuredCFGAnalysis()->IsMergeBlock(block->id())) {
+ return false;
+ }
+
+ // The block must have exactly one predecessor.
+ auto predecessors = ir_context->cfg()->preds(block->id());
+ if (predecessors.size() != 1) {
+ return false;
+ }
+
+ uint32_t pred_id = predecessors[0];
+ auto predecessor = ir_context->get_instr_block(pred_id);
+
+ // The predecessor must not be the header of a construct and it must end with
+ // OpBranch.
+ if (predecessor->GetMergeInst() != nullptr ||
+ predecessor->terminator()->opcode() != SpvOpBranch) {
+ return false;
+ }
+
+ return true;
+}
+
+void TransformationReplaceOpSelectWithConditionalBranch::Apply(
+ opt::IRContext* ir_context, TransformationContext* /* unused */) const {
+ auto instruction =
+ ir_context->get_def_use_mgr()->GetDef(message_.select_id());
+
+ auto block = ir_context->get_instr_block(instruction);
+
+ auto predecessor =
+ ir_context->get_instr_block(ir_context->cfg()->preds(block->id())[0]);
+
+ // Create a new block for each non-zero id in {|message_.true_branch_id|,
+ // |message_.false_branch_id|}. Make each newly-created block branch
+ // unconditionally to the instruction block.
+ for (uint32_t id : {message_.true_block_id(), message_.false_block_id()}) {
+ if (id) {
+ fuzzerutil::UpdateModuleIdBound(ir_context, id);
+
+ // Create the new block.
+ auto new_block = MakeUnique<opt::BasicBlock>(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpLabel, 0, id, opt::Instruction::OperandList{}));
+
+ // Add an unconditional branch from the new block to the instruction
+ // block.
+ new_block->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpBranch, 0, 0,
+ opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}}}));
+
+ // Insert the new block right after the predecessor of the instruction
+ // block.
+ block->GetParent()->InsertBasicBlockBefore(std::move(new_block), block);
+ }
+ }
+
+ // Delete the OpBranch instruction from the predecessor.
+ ir_context->KillInst(predecessor->terminator());
+
+ // Add an OpSelectionMerge instruction to the predecessor block, where the
+ // merge block is the instruction block.
+ predecessor->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpSelectionMerge, 0, 0,
+ opt::Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {block->id()}},
+ {SPV_OPERAND_TYPE_SELECTION_CONTROL,
+ {SpvSelectionControlMaskNone}}}));
+
+ // |if_block| will be the true block, if it has been created, the instruction
+ // block otherwise.
+ uint32_t if_block =
+ message_.true_block_id() ? message_.true_block_id() : block->id();
+
+ // |else_block| will be the false block, if it has been created, the
+ // instruction block otherwise.
+ uint32_t else_block =
+ message_.false_block_id() ? message_.false_block_id() : block->id();
+
+ assert(if_block != else_block &&
+ "|if_block| and |else_block| should always be different, if the "
+ "transformation is applicable.");
+
+ // Add a conditional branching instruction to the predecessor, branching to
+ // |if_block| if the condition is true and to |if_false| otherwise.
+ predecessor->AddInstruction(MakeUnique<opt::Instruction>(
+ ir_context, SpvOpBranchConditional, 0, 0,
+ opt::Instruction::OperandList{
+ {SPV_OPERAND_TYPE_ID, {instruction->GetSingleWordInOperand(0)}},
+ {SPV_OPERAND_TYPE_ID, {if_block}},
+ {SPV_OPERAND_TYPE_ID, {else_block}}}));
+
+ // |if_pred| will be the true block, if it has been created, the existing
+ // predecessor otherwise.
+ uint32_t if_pred =
+ message_.true_block_id() ? message_.true_block_id() : predecessor->id();
+
+ // |else_pred| will be the false block, if it has been created, the existing
+ // predecessor otherwise.
+ uint32_t else_pred =
+ message_.false_block_id() ? message_.false_block_id() : predecessor->id();
+
+ // Replace the OpSelect instruction in the merge block with an OpPhi.
+ // This: OpSelect %type %cond %if %else
+ // will become: OpPhi %type %if %if_pred %else %else_pred
+ instruction->SetOpcode(SpvOpPhi);
+ std::vector<opt::Operand> operands;
+
+ operands.emplace_back(instruction->GetInOperand(1));
+ operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {if_pred}});
+
+ operands.emplace_back(instruction->GetInOperand(2));
+ operands.emplace_back(opt::Operand{SPV_OPERAND_TYPE_ID, {else_pred}});
+
+ instruction->SetInOperands(std::move(operands));
+
+ // Invalidate all analyses, since the structure of the module was changed.
+ ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationReplaceOpSelectWithConditionalBranch::ToMessage() const {
+ protobufs::Transformation result;
+ *result.mutable_replace_opselect_with_conditional_branch() = message_;
+ return result;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_replace_opselect_with_conditional_branch.h b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
new file mode 100644
index 0000000..612c646
--- /dev/null
+++ b/source/fuzz/transformation_replace_opselect_with_conditional_branch.h
@@ -0,0 +1,61 @@
+// 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_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceOpSelectWithConditionalBranch
+ : public Transformation {
+ public:
+ explicit TransformationReplaceOpSelectWithConditionalBranch(
+ const protobufs::TransformationReplaceOpSelectWithConditionalBranch&
+ message);
+
+ TransformationReplaceOpSelectWithConditionalBranch(uint32_t select_id,
+ uint32_t true_block_id,
+ uint32_t false_block_id);
+
+ // - |message_.select_id| is the result id of an OpSelect instruction.
+ // - The condition of the OpSelect must be a scalar boolean.
+ // - The OpSelect instruction is the first instruction in its block.
+ // - The block containing the instruction is not a merge block, and it has a
+ // single predecessor, which is not a header and whose last instruction is
+ // OpBranch.
+ // - Each of |message_.true_block_id| and |message_.false_block_id| is either
+ // 0 or a valid fresh id, and at most one of them is 0. They must be
+ // distinct.
+ bool IsApplicable(
+ opt::IRContext* ir_context,
+ const TransformationContext& transformation_context) const override;
+
+ // Replaces the OpSelect instruction with id |message_.select_id| with a
+ // conditional branch and an OpPhi instruction.
+ void Apply(opt::IRContext* ir_context,
+ TransformationContext* transformation_context) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ protobufs::TransformationReplaceOpSelectWithConditionalBranch message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPSELECT_WITH_CONDITIONAL_BRANCH_H
diff --git a/source/fuzz/transformation_split_block.cpp b/source/fuzz/transformation_split_block.cpp
index 3c437e4..5e2baba 100644
--- a/source/fuzz/transformation_split_block.cpp
+++ b/source/fuzz/transformation_split_block.cpp
@@ -83,27 +83,9 @@
// Splitting the block must not separate the definition of an OpSampledImage
// from its use: the SPIR-V data rules require them to be in the same block.
- std::set<uint32_t> sampled_image_result_ids;
- bool before_split = true;
- for (auto& instruction : *block_to_split) {
- if (&instruction == &*split_before) {
- before_split = false;
- }
- if (before_split) {
- if (instruction.opcode() == SpvOpSampledImage) {
- sampled_image_result_ids.insert(instruction.result_id());
- }
- } else {
- if (!instruction.WhileEachInId(
- [&sampled_image_result_ids](uint32_t* id) -> bool {
- return !sampled_image_result_ids.count(*id);
- })) {
- return false;
- }
- }
- }
-
- return true;
+ return !fuzzerutil::
+ SplittingBeforeInstructionSeparatesOpSampledImageDefinitionFromUse(
+ block_to_split, instruction_to_split_before);
}
void TransformationSplitBlock::Apply(
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 34317d0..6199e69 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -87,6 +87,7 @@
transformation_replace_linear_algebra_instruction_test.cpp
transformation_replace_load_store_with_copy_memory_test.cpp
transformation_replace_opphi_id_from_dead_predecessor_test.cpp
+ transformation_replace_opselect_with_conditional_branch_test.cpp
transformation_replace_parameter_with_global_test.cpp
transformation_replace_params_with_struct_test.cpp
transformation_set_function_control_test.cpp
diff --git a/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp
new file mode 100644
index 0000000..b6a5138
--- /dev/null
+++ b/test/fuzz/transformation_replace_opselect_with_conditional_branch_test.cpp
@@ -0,0 +1,278 @@
+// 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_replace_opselect_with_conditional_branch.h"
+
+#include "test/fuzz/fuzz_test_util.h"
+
+#include "source/fuzz/fuzzer_pass_replace_opselects_with_conditional_branches.h"
+#include "source/fuzz/pseudo_random_generator.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Inapplicable) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 1
+ %7 = OpConstant %5 2
+ %8 = OpTypeVector %5 4
+ %9 = OpConstantNull %8
+ %10 = OpConstantComposite %8 %6 %6 %7 %7
+ %11 = OpTypeBool
+ %12 = OpTypeVector %11 4
+ %13 = OpConstantTrue %11
+ %14 = OpConstantFalse %11
+ %15 = OpConstantComposite %12 %13 %14 %14 %13
+ %2 = OpFunction %3 None %4
+ %16 = OpLabel
+ %17 = OpCopyObject %5 %6
+ %18 = OpCopyObject %5 %7
+ OpBranch %19
+ %19 = OpLabel
+ %20 = OpCopyObject %5 %17
+ %21 = OpSelect %5 %13 %17 %18
+ OpBranch %22
+ %22 = OpLabel
+ %23 = OpSelect %8 %15 %9 %10
+ OpBranch %24
+ %24 = OpLabel
+ OpSelectionMerge %25 None
+ OpBranchConditional %13 %26 %27
+ %26 = OpLabel
+ %28 = OpSelect %5 %13 %17 %18
+ OpBranch %27
+ %27 = OpLabel
+ %29 = OpSelect %5 %13 %17 %18
+ OpBranch %25
+ %25 = OpLabel
+ %30 = OpSelect %5 %13 %17 %18
+ OpBranch %31
+ %31 = OpLabel
+ OpLoopMerge %32 %33 None
+ OpBranch %33
+ %33 = OpLabel
+ %34 = OpSelect %5 %13 %17 %18
+ OpBranchConditional %13 %31 %32
+ %32 = OpLabel
+ %35 = OpSelect %5 %13 %17 %18
+ OpBranch %36
+ %36 = OpLabel
+ %37 = OpSelect %5 %13 %17 %18
+ OpReturn
+ OpFunctionEnd
+)";
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ 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);
+
+ // %20 is not an OpSelect instruction.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // %21 is not the first instruction in its block.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(21, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The condition for %23 is not a scalar, but a vector of booleans.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(23, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The predecessor (%24) of the block containing %28 is the header of a
+ // selection construct and does not branch unconditionally.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(24, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The block containing %29 has two predecessors (%24 and %26).
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(29, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The block containing %30 is the merge block for a selection construct.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(30, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The predecessor (%31) of the block containing %34 is a loop header.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(31, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+ // The block containing %35 is the merge block for a loop construct.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(35, 100, 101)
+ .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+ // |true_block_id| and |false_block_id| are both 0.
+ ASSERT_DEATH(
+ TransformationReplaceOpSelectWithConditionalBranch(37, 0, 0).IsApplicable(
+ context.get(), transformation_context),
+ "At least one of the ids must be non-zero.");
+#endif
+
+ // The fresh ids are not distinct.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 100)
+ .IsApplicable(context.get(), transformation_context));
+
+ // One of the ids is not fresh.
+ ASSERT_FALSE(TransformationReplaceOpSelectWithConditionalBranch(37, 100, 10)
+ .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceOpSelectWithConditionalBranchTest, Simple) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 1
+ %7 = OpConstant %5 2
+ %8 = OpTypeVector %5 4
+ %9 = OpConstantNull %8
+ %10 = OpConstantComposite %8 %6 %6 %7 %7
+ %11 = OpTypeBool
+ %12 = OpTypeVector %11 4
+ %13 = OpConstantTrue %11
+ %14 = OpConstantFalse %11
+ %15 = OpConstantComposite %12 %13 %14 %14 %13
+ %2 = OpFunction %3 None %4
+ %16 = OpLabel
+ %17 = OpCopyObject %5 %6
+ %18 = OpCopyObject %5 %7
+ OpBranch %19
+ %19 = OpLabel
+ %20 = OpSelect %5 %13 %17 %18
+ OpSelectionMerge %21 None
+ OpBranchConditional %13 %22 %21
+ %22 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpSelect %8 %13 %9 %10
+ OpBranch %21
+ %21 = OpLabel
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpSelect %5 %13 %17 %18
+ OpReturn
+ OpFunctionEnd
+)";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_5;
+ 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);
+
+ auto transformation =
+ TransformationReplaceOpSelectWithConditionalBranch(20, 100, 101);
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+
+ auto transformation2 =
+ TransformationReplaceOpSelectWithConditionalBranch(24, 0, 102);
+ ASSERT_TRUE(
+ transformation2.IsApplicable(context.get(), transformation_context));
+ transformation2.Apply(context.get(), &transformation_context);
+
+ auto transformation3 =
+ TransformationReplaceOpSelectWithConditionalBranch(26, 103, 0);
+ ASSERT_TRUE(
+ transformation3.IsApplicable(context.get(), transformation_context));
+ transformation3.Apply(context.get(), &transformation_context);
+
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %2 "main"
+ OpExecutionMode %2 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %2 "main"
+ %3 = OpTypeVoid
+ %4 = OpTypeFunction %3
+ %5 = OpTypeInt 32 1
+ %6 = OpConstant %5 1
+ %7 = OpConstant %5 2
+ %8 = OpTypeVector %5 4
+ %9 = OpConstantNull %8
+ %10 = OpConstantComposite %8 %6 %6 %7 %7
+ %11 = OpTypeBool
+ %12 = OpTypeVector %11 4
+ %13 = OpConstantTrue %11
+ %14 = OpConstantFalse %11
+ %15 = OpConstantComposite %12 %13 %14 %14 %13
+ %2 = OpFunction %3 None %4
+ %16 = OpLabel
+ %17 = OpCopyObject %5 %6
+ %18 = OpCopyObject %5 %7
+ OpSelectionMerge %19 None
+ OpBranchConditional %13 %100 %101
+ %100 = OpLabel
+ OpBranch %19
+ %101 = OpLabel
+ OpBranch %19
+ %19 = OpLabel
+ %20 = OpPhi %5 %17 %100 %18 %101
+ OpSelectionMerge %21 None
+ OpBranchConditional %13 %22 %21
+ %22 = OpLabel
+ OpSelectionMerge %23 None
+ OpBranchConditional %13 %23 %102
+ %102 = OpLabel
+ OpBranch %23
+ %23 = OpLabel
+ %24 = OpPhi %8 %9 %22 %10 %102
+ OpBranch %21
+ %21 = OpLabel
+ OpSelectionMerge %25 None
+ OpBranchConditional %13 %103 %25
+ %103 = OpLabel
+ OpBranch %25
+ %25 = OpLabel
+ %26 = OpPhi %5 %17 %103 %18 %21
+ OpReturn
+ OpFunctionEnd
+)";
+
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools