spirv-fuzz: Permute OpPhi instruction operands (#3421)

Fixes #3415.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 547256f..fd6c5c9 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -64,6 +64,7 @@
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
         fuzzer_pass_permute_function_parameters.h
+        fuzzer_pass_permute_phi_operands.h
         fuzzer_pass_push_ids_through_variables.h
         fuzzer_pass_replace_linear_algebra_instructions.h
         fuzzer_pass_split_blocks.h
@@ -116,6 +117,7 @@
         transformation_move_block_down.h
         transformation_outline_function.h
         transformation_permute_function_parameters.h
+        transformation_permute_phi_operands.h
         transformation_push_id_through_variable.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
@@ -168,6 +170,7 @@
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
         fuzzer_pass_permute_function_parameters.cpp
+        fuzzer_pass_permute_phi_operands.cpp
         fuzzer_pass_push_ids_through_variables.cpp
         fuzzer_pass_replace_linear_algebra_instructions.cpp
         fuzzer_pass_split_blocks.cpp
@@ -219,6 +222,7 @@
         transformation_move_block_down.cpp
         transformation_outline_function.cpp
         transformation_permute_function_parameters.cpp
+        transformation_permute_phi_operands.cpp
         transformation_push_id_through_variable.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 3d738f7..26585c7 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -47,6 +47,7 @@
 #include "source/fuzz/fuzzer_pass_outline_functions.h"
 #include "source/fuzz/fuzzer_pass_permute_blocks.h"
 #include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
 #include "source/fuzz/fuzzer_pass_push_ids_through_variables.h"
 #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
@@ -305,6 +306,9 @@
   MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
       &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
+  MaybeAddPass<FuzzerPassPermutePhiOperands>(
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
+      transformation_sequence_out);
   MaybeAddPass<FuzzerPassSwapCommutableOperands>(
       &final_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 58ec531..7f10642 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -63,6 +63,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
 const std::pair<uint32_t, uint32_t>
@@ -165,6 +166,8 @@
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
   chance_of_permuting_parameters_ =
       ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
+  chance_of_permuting_phi_operands_ =
+      ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
   chance_of_pushing_id_through_variable_ =
       ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
   chance_of_replacing_id_with_synonym_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index d73f814..f4bf1a7 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -185,6 +185,9 @@
   uint32_t GetChanceOfPermutingParameters() {
     return chance_of_permuting_parameters_;
   }
+  uint32_t GetChanceOfPermutingPhiOperands() {
+    return chance_of_permuting_phi_operands_;
+  }
   uint32_t GetChanceOfPushingIdThroughVariable() {
     return chance_of_pushing_id_through_variable_;
   }
@@ -290,6 +293,7 @@
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
   uint32_t chance_of_permuting_parameters_;
+  uint32_t chance_of_permuting_phi_operands_;
   uint32_t chance_of_pushing_id_through_variable_;
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_replacing_linear_algebra_instructions_;
diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.cpp b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
new file mode 100644
index 0000000..c241d9d
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_phi_operands.cpp
@@ -0,0 +1,64 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 <numeric>
+#include <vector>
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_permute_phi_operands.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_permute_phi_operands.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermutePhiOperands::FuzzerPassPermutePhiOperands(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassPermutePhiOperands::~FuzzerPassPermutePhiOperands() = default;
+
+void FuzzerPassPermutePhiOperands::Apply() {
+  ForEachInstructionWithInstructionDescriptor(
+      [this](opt::Function* /*unused*/, opt::BasicBlock* /*unused*/,
+             opt::BasicBlock::iterator inst_it,
+             const protobufs::InstructionDescriptor& /*unused*/) {
+        const auto& inst = *inst_it;
+
+        if (inst.opcode() != SpvOpPhi) {
+          return;
+        }
+
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfPermutingPhiOperands())) {
+          return;
+        }
+
+        // Create a vector of indices for each pair of operands in the |inst|.
+        // OpPhi always has an even number of operands.
+        std::vector<uint32_t> permutation(inst.NumInOperands() / 2);
+        std::iota(permutation.begin(), permutation.end(), 0);
+        GetFuzzerContext()->Shuffle(&permutation);
+
+        ApplyTransformation(TransformationPermutePhiOperands(
+            inst.result_id(), std::move(permutation)));
+      });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_phi_operands.h b/source/fuzz/fuzzer_pass_permute_phi_operands.h
new file mode 100644
index 0000000..974c2c1
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_phi_operands.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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_PERMUTE_PHI_OPERANDS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_PHI_OPERANDS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Iterates over all instructions in the module and randomly decides for each
+// OpPhi instruction whether to permute its operands.
+class FuzzerPassPermutePhiOperands : public FuzzerPass {
+ public:
+  FuzzerPassPermutePhiOperands(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermutePhiOperands() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_PHI_OPERANDS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 435ac04..d439d47 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -12,6 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
+#include <unordered_set>
+
 #include "source/fuzz/fuzzer_util.h"
 
 #include "source/opt/build_module.h"
@@ -652,6 +655,26 @@
           {SPV_OPERAND_TYPE_ID, {initializer_id}}}));
 }
 
+bool HasDuplicates(const std::vector<uint32_t>& arr) {
+  return std::unordered_set<uint32_t>(arr.begin(), arr.end()).size() !=
+         arr.size();
+}
+
+bool IsPermutationOfRange(const std::vector<uint32_t>& arr, uint32_t lo,
+                          uint32_t hi) {
+  if (arr.empty()) {
+    return lo > hi;
+  }
+
+  if (HasDuplicates(arr)) {
+    return false;
+  }
+
+  auto min_max = std::minmax_element(arr.begin(), arr.end());
+  return arr.size() == hi - lo + 1 && *min_max.first == lo &&
+         *min_max.second == hi;
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 77e390e..99cfff9 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -252,6 +252,15 @@
                       uint32_t type_id, uint32_t function_id,
                       uint32_t initializer_id);
 
+// Returns true if the vector |arr| has duplicates.
+bool HasDuplicates(const std::vector<uint32_t>& arr);
+
+// Checks that the given vector |arr| contains a permutation of a range
+// [lo, hi]. That being said, all elements in the range are present without
+// duplicates. If |arr| is empty, returns true iff |lo > hi|.
+bool IsPermutationOfRange(const std::vector<uint32_t>& arr, uint32_t lo,
+                          uint32_t hi);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 9ebabf7..71273e7 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -379,6 +379,7 @@
     TransformationAddSpecConstantOp add_spec_constant_op = 48;
     TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 49;
     TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50;
+    TransformationPermutePhiOperands permute_phi_operands = 51;
     // Add additional option using the next available number.
   }
 }
@@ -1005,6 +1006,19 @@
 
 }
 
+message TransformationPermutePhiOperands {
+
+  // Permutes operands of some OpPhi instruction.
+
+  // Result id of the instruction to apply the transformation to.
+  uint32 result_id = 1;
+
+  // A sequence of numbers in the range [0, n/2 - 1] where |n| is the number
+  // of operands of the OpPhi instruction with |result_id|.
+  repeated uint32 permutation = 2;
+
+}
+
 message TransformationPushIdThroughVariable {
 
   // A transformation that makes |value_synonym_id| and |value_id| to be
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index da08e88..686b46f 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -52,6 +52,7 @@
 #include "source/fuzz/transformation_move_block_down.h"
 #include "source/fuzz/transformation_outline_function.h"
 #include "source/fuzz/transformation_permute_function_parameters.h"
+#include "source/fuzz/transformation_permute_phi_operands.h"
 #include "source/fuzz/transformation_push_id_through_variable.h"
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
@@ -170,6 +171,9 @@
         kPermuteFunctionParameters:
       return MakeUnique<TransformationPermuteFunctionParameters>(
           message.permute_function_parameters());
+    case protobufs::Transformation::TransformationCase::kPermutePhiOperands:
+      return MakeUnique<TransformationPermutePhiOperands>(
+          message.permute_phi_operands());
     case protobufs::Transformation::TransformationCase::kPushIdThroughVariable:
       return MakeUnique<TransformationPushIdThroughVariable>(
           message.push_id_through_variable());
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
index 0f1220e..4b9561c 100644
--- a/source/fuzz/transformation_permute_function_parameters.cpp
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -12,7 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include <unordered_set>
 #include <vector>
 
 #include "source/fuzz/fuzzer_util.h"
@@ -53,7 +52,8 @@
   const auto* function_type = fuzzerutil::GetFunctionType(ir_context, function);
   assert(function_type && "Function type is null");
 
-  const auto& permutation = message_.permutation();
+  std::vector<uint32_t> permutation(message_.permutation().begin(),
+                                    message_.permutation().end());
 
   // Don't take return type into account
   auto arg_size = function_type->NumInOperands() - 1;
@@ -63,21 +63,20 @@
     return false;
   }
 
-  // Check that all indices are valid
-  // and unique integers from the [0, n-1] set
-  std::unordered_set<uint32_t> unique_indices;
-  for (auto index : permutation) {
-    // We don't compare |index| with 0 since it's an unsigned integer
-    if (index >= arg_size) {
-      return false;
-    }
+  // Check that permutation doesn't have duplicated values.
+  assert(!fuzzerutil::HasDuplicates(permutation) &&
+         "Permutation has duplicates");
 
-    unique_indices.insert(index);
+  // Check that elements in permutation are in range [0, arg_size - 1].
+  //
+  // We must check whether the permutation is empty first because in that case
+  // |arg_size - 1| will produce |std::numeric_limits<uint32_t>::max()| since
+  // it's an unsigned integer.
+  if (!permutation.empty() &&
+      !fuzzerutil::IsPermutationOfRange(permutation, 0, arg_size - 1)) {
+    return false;
   }
 
-  // Check that permutation doesn't have duplicated values
-  assert(unique_indices.size() == arg_size && "Permutation has duplicates");
-
   // Check that new function's type is valid:
   //   - Has the same number of operands
   //   - Has the same result type as the old one
diff --git a/source/fuzz/transformation_permute_phi_operands.cpp b/source/fuzz/transformation_permute_phi_operands.cpp
new file mode 100644
index 0000000..95e7a1f
--- /dev/null
+++ b/source/fuzz/transformation_permute_phi_operands.cpp
@@ -0,0 +1,94 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_permute_phi_operands.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPermutePhiOperands::TransformationPermutePhiOperands(
+    const spvtools::fuzz::protobufs::TransformationPermutePhiOperands& message)
+    : message_(message) {}
+
+TransformationPermutePhiOperands::TransformationPermutePhiOperands(
+    uint32_t result_id, const std::vector<uint32_t>& permutation) {
+  message_.set_result_id(result_id);
+
+  for (auto index : permutation) {
+    message_.add_permutation(index);
+  }
+}
+
+bool TransformationPermutePhiOperands::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // Check that |message_.result_id| is valid.
+  const auto* inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+  if (!inst || inst->opcode() != SpvOpPhi) {
+    return false;
+  }
+
+  // Check that |message_.permutation| has expected size.
+  auto expected_permutation_size = inst->NumInOperands() / 2;
+  if (static_cast<uint32_t>(message_.permutation().size()) !=
+      expected_permutation_size) {
+    return false;
+  }
+
+  // Check that |message_.permutation| has elements in range
+  // [0, expected_permutation_size - 1].
+  std::vector<uint32_t> permutation(message_.permutation().begin(),
+                                    message_.permutation().end());
+  assert(!fuzzerutil::HasDuplicates(permutation) &&
+         "Permutation has duplicates");
+
+  // We must check whether the permutation is empty first because in that case
+  // |expected_permutation_size - 1| will produce
+  // |std::numeric_limits<uint32_t>::max()| since it's an unsigned integer.
+  return permutation.empty() ||
+         fuzzerutil::IsPermutationOfRange(permutation, 0,
+                                          expected_permutation_size - 1);
+}
+
+void TransformationPermutePhiOperands::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  auto* inst = ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+  assert(inst);
+
+  opt::Instruction::OperandList permuted_operands;
+  permuted_operands.reserve(inst->NumInOperands());
+
+  for (auto index : message_.permutation()) {
+    permuted_operands.push_back(std::move(inst->GetInOperand(2 * index)));
+    permuted_operands.push_back(std::move(inst->GetInOperand(2 * index + 1)));
+  }
+
+  inst->SetInOperands(std::move(permuted_operands));
+
+  // Make sure our changes are analyzed
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPermutePhiOperands::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_permute_phi_operands() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_permute_phi_operands.h b/source/fuzz/transformation_permute_phi_operands.h
new file mode 100644
index 0000000..df242e3
--- /dev/null
+++ b/source/fuzz/transformation_permute_phi_operands.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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_PERMUTE_PHI_OPERANDS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PERMUTE_PHI_OPERANDS_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 TransformationPermutePhiOperands : public Transformation {
+ public:
+  explicit TransformationPermutePhiOperands(
+      const protobufs::TransformationPermutePhiOperands& message);
+
+  TransformationPermutePhiOperands(uint32_t result_id,
+                                   const std::vector<uint32_t>& permutation);
+
+  // - |result_id| must be a valid id of some OpPhi instruction in the module.
+  // - |permutation| must contain elements in the range [0, n/2 - 1] where |n|
+  //   is a number of operands to the instruction with |result_id|. All elements
+  //   must be unique (i.e. |permutation.size() == n / 2|).
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Permutes operands of the OpPhi instruction with |result_id| according to
+  // the elements in |permutation|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationPermutePhiOperands message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PERMUTE_PHI_OPERANDS_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index dd3c181..9a59742 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -58,6 +58,7 @@
           transformation_move_block_down_test.cpp
           transformation_outline_function_test.cpp
           transformation_permute_function_parameters_test.cpp
+          transformation_permute_phi_operands_test.cpp
           transformation_push_id_through_variable_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
index a4a7c00..13daff6 100644
--- a/test/fuzz/transformation_permute_function_parameters_test.cpp
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -220,10 +220,17 @@
   ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {0, 1})
                    .IsApplicable(context.get(), transformation_context));
 
-  // Permutation has invalid values
+  // Permutation has invalid values 1
   ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {3, 1, 0})
                    .IsApplicable(context.get(), transformation_context));
 
+#ifndef NDEBUG
+  // Permutation has invalid values 2
+  ASSERT_DEATH(TransformationPermuteFunctionParameters(22, 0, {2, 2, 1})
+                   .IsApplicable(context.get(), transformation_context),
+               "Permutation has duplicates");
+#endif
+
   // Type id is not an OpTypeFunction instruction
   ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 42, {2, 1, 0})
                    .IsApplicable(context.get(), transformation_context));
diff --git a/test/fuzz/transformation_permute_phi_operands_test.cpp b/test/fuzz/transformation_permute_phi_operands_test.cpp
new file mode 100644
index 0000000..c0a428a
--- /dev/null
+++ b/test/fuzz/transformation_permute_phi_operands_test.cpp
@@ -0,0 +1,154 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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_permute_phi_operands.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPermutePhiOperandsTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %11 = OpConstant %6 1
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %12 = OpLoad %6 %8
+         %13 = OpLoad %6 %10
+         %15 = OpSLessThan %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %21
+         %16 = OpLabel
+         %18 = OpLoad %6 %10
+         %19 = OpLoad %6 %8
+         %20 = OpIAdd %6 %19 %18
+               OpBranch %17
+         %21 = OpLabel
+         %22 = OpLoad %6 %10
+         %23 = OpLoad %6 %8
+         %24 = OpISub %6 %23 %22
+               OpBranch %17
+         %17 = OpLabel
+         %25 = OpPhi %6 %20 %16 %24 %21
+               OpStore %8 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  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);
+
+  // Result id is invalid.
+  ASSERT_FALSE(TransformationPermutePhiOperands(26, {}).IsApplicable(
+      context.get(), transformation_context));
+
+  // Result id is not of an OpPhi instruction.
+  ASSERT_FALSE(TransformationPermutePhiOperands(24, {}).IsApplicable(
+      context.get(), transformation_context));
+
+  // Result id is not of an OpPhi instruction.
+  ASSERT_FALSE(TransformationPermutePhiOperands(24, {}).IsApplicable(
+      context.get(), transformation_context));
+
+  // Permutation has invalid size.
+  ASSERT_FALSE(TransformationPermutePhiOperands(25, {0, 1, 2})
+                   .IsApplicable(context.get(), transformation_context));
+
+#ifndef NDEBUG
+  // Permutation has duplicates.
+  ASSERT_DEATH(TransformationPermutePhiOperands(25, {0, 0})
+                   .IsApplicable(context.get(), transformation_context),
+               "Permutation has duplicates");
+#endif
+
+  // Permutation's values are not in range.
+  ASSERT_FALSE(TransformationPermutePhiOperands(25, {1, 2})
+                   .IsApplicable(context.get(), transformation_context));
+
+  TransformationPermutePhiOperands transformation(25, {1, 0});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 0
+         %11 = OpConstant %6 1
+         %14 = OpTypeBool
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %12 = OpLoad %6 %8
+         %13 = OpLoad %6 %10
+         %15 = OpSLessThan %14 %12 %13
+               OpSelectionMerge %17 None
+               OpBranchConditional %15 %16 %21
+         %16 = OpLabel
+         %18 = OpLoad %6 %10
+         %19 = OpLoad %6 %8
+         %20 = OpIAdd %6 %19 %18
+               OpBranch %17
+         %21 = OpLabel
+         %22 = OpLoad %6 %10
+         %23 = OpLoad %6 %8
+         %24 = OpISub %6 %23 %22
+               OpBranch %17
+         %17 = OpLabel
+         %25 = OpPhi %6 %24 %21 %20 %16
+               OpStore %8 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools