spirv-fuzz: Add fuzzer pass to permute function parameters (#3212)

Fixes #3194.
diff --git a/.gitignore b/.gitignore
index 196c63c..b2af56e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@
 # C-Lion
 /.idea/
 /cmake-build-*/
+
+# VSCode
+/.vscode/*
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 231ea75..8fd0593 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -62,6 +62,7 @@
         fuzzer_pass_obfuscate_constants.h
         fuzzer_pass_outline_functions.h
         fuzzer_pass_permute_blocks.h
+        fuzzer_pass_permute_function_parameters.h
         fuzzer_pass_split_blocks.h
         fuzzer_pass_swap_commutable_operands.h
         fuzzer_util.h
@@ -104,6 +105,7 @@
         transformation_merge_blocks.h
         transformation_move_block_down.h
         transformation_outline_function.h
+        transformation_permute_function_parameters.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_id_with_synonym.h
@@ -150,6 +152,7 @@
         fuzzer_pass_obfuscate_constants.cpp
         fuzzer_pass_outline_functions.cpp
         fuzzer_pass_permute_blocks.cpp
+        fuzzer_pass_permute_function_parameters.cpp
         fuzzer_pass_split_blocks.cpp
         fuzzer_pass_swap_commutable_operands.cpp
         fuzzer_util.cpp
@@ -191,6 +194,7 @@
         transformation_merge_blocks.cpp
         transformation_move_block_down.cpp
         transformation_outline_function.cpp
+        transformation_permute_function_parameters.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_id_with_synonym.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 945bcc5..a89c361 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -45,6 +45,7 @@
 #include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
 #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_split_blocks.h"
 #include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@@ -244,6 +245,9 @@
     MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
                                           &fact_manager, &fuzzer_context,
                                           transformation_sequence_out);
+    MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
+        &passes, ir_context.get(), &fact_manager, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassSplitBlocks>(&passes, ir_context.get(),
                                         &fact_manager, &fuzzer_context,
                                         transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index a374f29..dd2d7c3 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -60,6 +60,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
 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> kChanceOfReplacingIdWithSynonym = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
 
@@ -146,6 +147,8 @@
       ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
   chance_of_outlining_function_ =
       ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
+  chance_of_permuting_parameters_ =
+      ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
   chance_of_replacing_id_with_synonym_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
   chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 1d8b7bf..813f232 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -62,6 +62,40 @@
     return result;
   }
 
+  // Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively.
+  // |lo| and |hi| must be valid indices to the |sequence|
+  template <typename T>
+  void Shuffle(std::vector<T>* sequence, size_t lo, size_t hi) const {
+    auto& array = *sequence;
+
+    if (array.empty()) {
+      return;
+    }
+
+    assert(lo <= hi && hi < array.size() && "lo and/or hi indices are invalid");
+
+    // i > lo to account for potential infinite loop when lo == 0
+    for (size_t i = hi; i > lo; --i) {
+      auto index =
+          random_generator_->RandomUint32(static_cast<uint32_t>(i - lo + 1));
+
+      if (lo + index != i) {
+        // Introduce std::swap to the scope but don't use it
+        // directly since there might be a better overload
+        using std::swap;
+        swap(array[lo + index], array[i]);
+      }
+    }
+  }
+
+  // Ramdomly shuffles a |sequence|
+  template <typename T>
+  void Shuffle(std::vector<T>* sequence) const {
+    if (!sequence->empty()) {
+      Shuffle(sequence, 0, sequence->size() - 1);
+    }
+  }
+
   // Yields an id that is guaranteed not to be used in the module being fuzzed,
   // or to have been issued before.
   uint32_t GetFreshId();
@@ -139,6 +173,9 @@
   uint32_t GetChanceOfOutliningFunction() {
     return chance_of_outlining_function_;
   }
+  uint32_t GetChanceOfPermutingParameters() {
+    return chance_of_permuting_parameters_;
+  }
   uint32_t GetChanceOfReplacingIdWithSynonym() {
     return chance_of_replacing_id_with_synonym_;
   }
@@ -203,6 +240,7 @@
   uint32_t chance_of_moving_block_down_;
   uint32_t chance_of_obfuscating_constant_;
   uint32_t chance_of_outlining_function_;
+  uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_replacing_id_with_synonym_;
   uint32_t chance_of_splitting_block_;
 
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 73a0285..a76f10d 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -22,6 +22,7 @@
 #include "source/fuzz/transformation_add_global_undef.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
+#include "source/fuzz/transformation_add_type_function.h"
 #include "source/fuzz/transformation_add_type_int.h"
 #include "source/fuzz/transformation_add_type_matrix.h"
 #include "source/fuzz/transformation_add_type_pointer.h"
@@ -179,6 +180,26 @@
   return result;
 }
 
+uint32_t FuzzerPass::FindOrCreateFunctionType(
+    uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
+  // FindFunctionType has a sigle argument for OpTypeFunction operands
+  // so we will have to copy them all in this vector
+  std::vector<uint32_t> type_ids(argument_id.size() + 1);
+  type_ids[0] = return_type_id;
+  std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
+
+  // Check if type exists
+  auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
+  if (existing_id) {
+    return existing_id;
+  }
+
+  auto result = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(
+      TransformationAddTypeFunction(result, return_type_id, argument_id));
+  return result;
+}
+
 uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
                                             uint32_t component_count) {
   assert(component_count >= 2 && component_count <= 4 &&
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 910eed1..46ee408 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -112,6 +112,12 @@
   // instruction does not exist, a transformation is applied to add it.
   uint32_t FindOrCreate32BitFloatType();
 
+  // Returns the id of an OpTypeFunction %<return_type_id> %<...argument_id>
+  // instruction. If such an instruction doesn't exist, a transformation
+  // is applied to create a new one.
+  uint32_t FindOrCreateFunctionType(uint32_t return_type_id,
+                                    const std::vector<uint32_t>& argument_id);
+
   // Returns the id of an OpTypeVector instruction, with |component_type_id|
   // (which must already exist) as its base type, and |component_count|
   // elements (which must be in the range [2, 4]).  If such an instruction does
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
new file mode 100644
index 0000000..2c49860
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -0,0 +1,81 @@
+// 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_function_parameters.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteFunctionParameters::FuzzerPassPermuteFunctionParameters(
+    opt::IRContext* ir_context, FactManager* fact_manager,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassPermuteFunctionParameters::~FuzzerPassPermuteFunctionParameters() =
+    default;
+
+void FuzzerPassPermuteFunctionParameters::Apply() {
+  for (const auto& function : *GetIRContext()->module()) {
+    uint32_t function_id = function.result_id();
+
+    // Skip the function if it is an entry point
+    if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(), function_id)) {
+      continue;
+    }
+
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfPermutingParameters())) {
+      continue;
+    }
+
+    // Compute permutation for parameters
+    auto* function_type =
+        fuzzerutil::GetFunctionType(GetIRContext(), &function);
+    assert(function_type && "Function type is null");
+
+    // Don't take return type into account
+    uint32_t arg_size = function_type->NumInOperands() - 1;
+
+    // Create a vector, fill it with [0, n-1] values and shuffle it
+    std::vector<uint32_t> permutation(arg_size);
+    std::iota(permutation.begin(), permutation.end(), 0);
+    GetFuzzerContext()->Shuffle(&permutation);
+
+    // Create a new OpFunctionType instruction with permuted arguments
+    // if needed
+    auto result_type_id = function_type->GetSingleWordInOperand(0);
+    std::vector<uint32_t> argument_ids;
+
+    for (auto index : permutation) {
+      // +1 to take function's return type into account
+      argument_ids.push_back(function_type->GetSingleWordInOperand(index + 1));
+    }
+
+    // Apply our transformation
+    ApplyTransformation(TransformationPermuteFunctionParameters(
+        function_id, FindOrCreateFunctionType(result_type_id, argument_ids),
+        permutation));
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h
new file mode 100644
index 0000000..bc79804
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h
@@ -0,0 +1,45 @@
+// 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_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that, given a non-entry-point function taking n parameters
+// and a permutation of the set [0, n - 1]:
+//   1. Introduces a new function type that is the same as the original
+//      function's type but with the order of arguments permuted
+//      (only add this if it doesn't already exist)
+//   2. Changes the type of the function to this type
+//   3. Adjusts all calls to the function so that their arguments are permuted
+class FuzzerPassPermuteFunctionParameters : public FuzzerPass {
+ public:
+  FuzzerPassPermuteFunctionParameters(
+      opt::IRContext* ir_context, FactManager* fact_manager,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassPermuteFunctionParameters();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 26961c8..4bfa195 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -395,6 +395,12 @@
   return 0;
 }
 
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+                                  const opt::Function* function) {
+  uint32_t type_id = function->DefInst().GetSingleWordInOperand(1);
+  return context->get_def_use_mgr()->GetDef(type_id);
+}
+
 opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
   for (auto& function : *ir_context->module()) {
     if (function.result_id() == function_id) {
@@ -404,6 +410,15 @@
   return nullptr;
 }
 
+bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
+  for (auto& entry_point : context->module()->entry_points()) {
+    if (entry_point.GetSingleWordInOperand(1) == function_id) {
+      return true;
+    }
+  }
+  return false;
+}
+
 bool IdIsAvailableAtUse(opt::IRContext* context,
                         opt::Instruction* use_instruction,
                         uint32_t use_input_operand_index, uint32_t id) {
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index daa836c..ddc0d5a 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -152,10 +152,20 @@
 uint32_t FindFunctionType(opt::IRContext* ir_context,
                           const std::vector<uint32_t>& type_ids);
 
+// Returns a type instruction (OpTypeFunction) for |function|.
+// Returns |nullptr| if type is not found.
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+                                  const opt::Function* function);
+
 // Returns the function with result id |function_id|, or |nullptr| if no such
 // function exists.
 opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
 
+// Returns |true| if one of entry points has function id |function_id|.
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3218):
+// TransformationAddFunctionCall also has this functionality as a static method
+bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);
+
 // Checks whether |id| is available (according to dominance rules) at the use
 // point defined by input operand |use_input_operand_index| of
 // |use_instruction|.
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 25f0142..83dd2cf 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -370,6 +370,7 @@
     TransformationAccessChain access_chain = 39;
     TransformationEquationInstruction equation_instruction = 40;
     TransformationSwapCommutableOperands swap_commutable_operands = 41;
+    TransformationPermuteFunctionParameters permute_function_parameters = 42;
     // Add additional option using the next available number.
   }
 }
@@ -907,6 +908,36 @@
 
 }
 
+message TransformationPermuteFunctionParameters {
+
+  // A transformation that, given a non-entry-point function taking n
+  // parameters and a permutation of the set [0, n-1]:
+  //   - Introduces a new function type that is the same as the original
+  //     function's type but with the order of arguments permuted
+  //     (only if it doesn't already exist)
+  //   - Changes the type of the function to this type
+  //   - Adjusts all calls to the function so that their arguments are permuted
+
+  // Function, whose parameters will be permuted
+  uint32 function_id = 1;
+
+  // |new_type_id| is a result id of a valid OpTypeFunction instruction.
+  // New type is valid if:
+  //   - it has the same number of operands as the old one
+  //   - function's result type is the same as the old one
+  //   - function's arguments are permuted according to |permutation| vector
+  uint32 new_type_id = 2;
+
+  // An array of size |n|, where |n| is a number of arguments to a function
+  // with |function_id|. For each i: 0 <= permutation[i] < n.
+  //
+  // i-th element of this array contains a position for an i-th
+  // function's argument (i.e. i-th argument will be permutation[i]-th
+  // after running this transformation)
+  repeated uint32 permutation = 3;
+
+}
+
 message TransformationReplaceBooleanConstantWithConstantBinary {
 
   // A transformation to capture replacing a use of a boolean constant with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index fe0d41c..e06999c 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -47,6 +47,7 @@
 #include "source/fuzz/transformation_merge_blocks.h"
 #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_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/transformation_replace_id_with_synonym.h"
@@ -145,6 +146,10 @@
       return MakeUnique<TransformationOutlineFunction>(
           message.outline_function());
     case protobufs::Transformation::TransformationCase::
+        kPermuteFunctionParameters:
+      return MakeUnique<TransformationPermuteFunctionParameters>(
+          message.permute_function_parameters());
+    case protobufs::Transformation::TransformationCase::
         kReplaceBooleanConstantWithConstantBinary:
       return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
           message.replace_boolean_constant_with_constant_binary());
diff --git a/source/fuzz/transformation_add_type_function.h b/source/fuzz/transformation_add_type_function.h
index 2b59661..3880963 100644
--- a/source/fuzz/transformation_add_type_function.h
+++ b/source/fuzz/transformation_add_type_function.h
@@ -37,7 +37,7 @@
   // - |message_.return_type_id| and each element of |message_.argument_type_id|
   //   must be the ids of non-function types
   // - The module must not contain an OpTypeFunction instruction defining a
-  //   function type with the signature provided by teh given return and
+  //   function type with the signature provided by the given return and
   //   argument types
   bool IsApplicable(opt::IRContext* context,
                     const FactManager& fact_manager) const override;
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
new file mode 100644
index 0000000..2141533
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -0,0 +1,184 @@
+// 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 <unordered_set>
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPermuteFunctionParameters::
+    TransformationPermuteFunctionParameters(
+        const spvtools::fuzz::protobufs::
+            TransformationPermuteFunctionParameters& message)
+    : message_(message) {}
+
+TransformationPermuteFunctionParameters::
+    TransformationPermuteFunctionParameters(
+        uint32_t function_id, uint32_t new_type_id,
+        const std::vector<uint32_t>& permutation) {
+  message_.set_function_id(function_id);
+  message_.set_new_type_id(new_type_id);
+
+  for (auto index : permutation) {
+    message_.add_permutation(index);
+  }
+}
+
+bool TransformationPermuteFunctionParameters::IsApplicable(
+    opt::IRContext* context, const FactManager& /*unused*/) const {
+  // Check that function exists
+  const auto* function =
+      fuzzerutil::FindFunction(context, message_.function_id());
+  if (!function || function->DefInst().opcode() != SpvOpFunction ||
+      fuzzerutil::FunctionIsEntryPoint(context, function->result_id())) {
+    return false;
+  }
+
+  // Check that permutation has valid indices
+  const auto* function_type = fuzzerutil::GetFunctionType(context, function);
+  assert(function_type && "Function type is null");
+
+  const auto& permutation = message_.permutation();
+
+  // Don't take return type into account
+  auto arg_size = function_type->NumInOperands() - 1;
+
+  // |permutation| vector should be equal to the number of arguments
+  if (static_cast<uint32_t>(permutation.size()) != arg_size) {
+    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;
+    }
+
+    unique_indices.insert(index);
+  }
+
+  // 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
+  //   - Order of arguments is permuted
+  auto new_type_id = message_.new_type_id();
+  const auto* new_type = context->get_def_use_mgr()->GetDef(new_type_id);
+
+  if (!new_type || new_type->opcode() != SpvOpTypeFunction ||
+      new_type->NumInOperands() != function_type->NumInOperands()) {
+    return false;
+  }
+
+  // Check that both instructions have the same result type
+  if (new_type->GetSingleWordInOperand(0) !=
+      function_type->GetSingleWordInOperand(0)) {
+    return false;
+  }
+
+  // Check that new function type has its arguments permuted
+  for (int i = 0, n = static_cast<int>(permutation.size()); i < n; ++i) {
+    // +1 to take return type into account
+    if (new_type->GetSingleWordInOperand(i + 1) !=
+        function_type->GetSingleWordInOperand(permutation[i] + 1)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationPermuteFunctionParameters::Apply(
+    opt::IRContext* context, FactManager* /*unused*/) const {
+  // Retrieve all data from the message
+  uint32_t function_id = message_.function_id();
+  uint32_t new_type_id = message_.new_type_id();
+  const auto& permutation = message_.permutation();
+
+  // Find the function that will be transformed
+  auto* function = fuzzerutil::FindFunction(context, function_id);
+  assert(function && "Can't find the function");
+
+  // Change function's type
+  function->DefInst().SetInOperand(1, {new_type_id});
+
+  // Adjust OpFunctionParameter instructions
+
+  // Collect ids and types from OpFunctionParameter instructions
+  std::vector<uint32_t> param_id, param_type;
+  function->ForEachParam(
+      [&param_id, &param_type](const opt::Instruction* param) {
+        param_id.push_back(param->result_id());
+        param_type.push_back(param->type_id());
+      });
+
+  // Permute parameters' ids and types
+  std::vector<uint32_t> permuted_param_id, permuted_param_type;
+  for (auto index : permutation) {
+    permuted_param_id.push_back(param_id[index]);
+    permuted_param_type.push_back(param_type[index]);
+  }
+
+  // Set OpFunctionParameter instructions to point to new parameters
+  size_t i = 0;
+  function->ForEachParam(
+      [&i, &permuted_param_id, &permuted_param_type](opt::Instruction* param) {
+        param->SetResultType(permuted_param_type[i]);
+        param->SetResultId(permuted_param_id[i]);
+        ++i;
+      });
+
+  // Fix all OpFunctionCall instructions
+  context->get_def_use_mgr()->ForEachUser(
+      &function->DefInst(),
+      [function_id, &permutation](opt::Instruction* call) {
+        if (call->opcode() != SpvOpFunctionCall ||
+            call->GetSingleWordInOperand(0) != function_id) {
+          return;
+        }
+
+        opt::Instruction::OperandList call_operands = {
+            call->GetInOperand(0)  // Function id
+        };
+
+        for (auto index : permutation) {
+          // Take function id into account
+          call_operands.push_back(call->GetInOperand(index + 1));
+        }
+
+        call->SetInOperands(std::move(call_operands));
+      });
+
+  // Make sure our changes are analyzed
+  context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPermuteFunctionParameters::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_permute_function_parameters() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
new file mode 100644
index 0000000..c67a735
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -0,0 +1,61 @@
+// 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_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationPermuteFunctionParameters : public Transformation {
+ public:
+  explicit TransformationPermuteFunctionParameters(
+      const protobufs::TransformationPermuteFunctionParameters& message);
+
+  TransformationPermuteFunctionParameters(
+      uint32_t function_id, uint32_t new_type_id,
+      const std::vector<uint32_t>& permutation);
+
+  // - |function_id| is a valid non-entry-point OpFunction instruction
+  // - |new_type_id| is a result id of a valid OpTypeFunction instruction.
+  //   New type is valid if:
+  //     - it has the same number of operands as the old one
+  //     - function's result type is the same as the old one
+  //     - function's arguments are permuted according to |permutation| vector
+  // - |permutation| is a set of [0..(n - 1)], where n is a number of arguments
+  //   to the function
+  bool IsApplicable(opt::IRContext* context,
+                    const FactManager& fact_manager) const override;
+
+  // - OpFunction instruction with |result_id == function_id| is changed.
+  //   Its arguments are permuted according to the |permutation| vector
+  // - Changed function gets a new type specified by |type_id|
+  // - Calls to the function are adjusted accordingly
+  void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationPermuteFunctionParameters message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 40aa152..1e26e74 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -54,6 +54,7 @@
           transformation_merge_blocks_test.cpp
           transformation_move_block_down_test.cpp
           transformation_outline_function_test.cpp
+          transformation_permute_function_parameters_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
           transformation_replace_id_with_synonym_test.cpp
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
new file mode 100644
index 0000000..1af4699
--- /dev/null
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -0,0 +1,430 @@
+// 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_function_parameters.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
+  std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %72 %74
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "g(f1;f1;"
+               OpName %10 "x"
+               OpName %11 "y"
+               OpName %22 "f(f1;i1;vf2;"
+               OpName %19 "x"
+               OpName %20 "y"
+               OpName %21 "z"
+               OpName %28 "cond(i1;f1;"
+               OpName %26 "a"
+               OpName %27 "b"
+               OpName %53 "param"
+               OpName %54 "param"
+               OpName %66 "param"
+               OpName %67 "param"
+               OpName %72 "color"
+               OpName %74 "gl_FragCoord"
+               OpName %75 "param"
+               OpName %79 "param"
+               OpName %85 "param"
+               OpName %86 "param"
+               OpName %91 "param"
+               OpName %92 "param"
+               OpName %93 "param"
+               OpName %99 "param"
+               OpName %100 "param"
+               OpName %101 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %72 Location 0
+               OpDecorate %74 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeVector %6 4
+          %9 = OpTypeFunction %8 %7 %7
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %16 = OpTypeVector %6 2
+         %17 = OpTypePointer Function %16
+         %18 = OpTypeFunction %8 %7 %15 %17
+         %24 = OpTypeBool
+         %25 = OpTypeFunction %24 %15 %7
+         %105 = OpTypeFunction %24 %7 %15    ; predefined type for %28
+         %31 = OpConstant %6 255
+         %33 = OpConstant %6 0
+         %34 = OpConstant %6 1
+         %42 = OpTypeInt 32 0
+         %43 = OpConstant %42 0
+         %49 = OpConstant %42 1
+         %64 = OpConstant %14 4
+         %65 = OpConstant %6 5
+         %71 = OpTypePointer Output %8
+         %72 = OpVariable %71 Output
+         %73 = OpTypePointer Input %8
+         %74 = OpVariable %73 Input
+         %76 = OpTypePointer Input %6
+         %84 = OpConstant %14 5
+         %90 = OpConstant %6 3
+         %98 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %66 = OpVariable %15 Function
+         %67 = OpVariable %7 Function
+         %75 = OpVariable %7 Function
+         %79 = OpVariable %7 Function
+         %85 = OpVariable %15 Function
+         %86 = OpVariable %7 Function
+         %91 = OpVariable %7 Function
+         %92 = OpVariable %15 Function
+         %93 = OpVariable %17 Function
+         %99 = OpVariable %7 Function
+        %100 = OpVariable %15 Function
+        %101 = OpVariable %17 Function
+               OpStore %66 %64
+               OpStore %67 %65
+         %68 = OpFunctionCall %24 %28 %66 %67
+               OpSelectionMerge %70 None
+               OpBranchConditional %68 %69 %83
+         %69 = OpLabel
+         %77 = OpAccessChain %76 %74 %43
+         %78 = OpLoad %6 %77
+               OpStore %75 %78
+         %80 = OpAccessChain %76 %74 %49
+         %81 = OpLoad %6 %80
+               OpStore %79 %81
+         %82 = OpFunctionCall %8 %12 %75 %79
+               OpStore %72 %82
+               OpBranch %70
+         %83 = OpLabel
+               OpStore %85 %84
+               OpStore %86 %65
+         %87 = OpFunctionCall %24 %28 %85 %86
+               OpSelectionMerge %89 None
+               OpBranchConditional %87 %88 %97
+         %88 = OpLabel
+               OpStore %91 %90
+               OpStore %92 %64
+         %94 = OpLoad %8 %74
+         %95 = OpVectorShuffle %16 %94 %94 0 1
+               OpStore %93 %95
+         %96 = OpFunctionCall %8 %22 %91 %92 %93
+               OpStore %72 %96
+               OpBranch %89
+         %97 = OpLabel
+               OpStore %99 %98
+               OpStore %100 %84
+        %102 = OpLoad %8 %74
+        %103 = OpVectorShuffle %16 %102 %102 0 1
+               OpStore %101 %103
+        %104 = OpFunctionCall %8 %22 %99 %100 %101
+               OpStore %72 %104
+               OpBranch %89
+         %89 = OpLabel
+               OpBranch %70
+         %70 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %9
+         %10 = OpFunctionParameter %7
+         %11 = OpFunctionParameter %7
+         %13 = OpLabel
+         %30 = OpLoad %6 %10
+         %32 = OpFDiv %6 %30 %31
+         %35 = OpLoad %6 %11
+         %36 = OpFDiv %6 %35 %31
+         %37 = OpFSub %6 %34 %36
+         %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+               OpReturnValue %38
+               OpFunctionEnd
+         %22 = OpFunction %8 None %18
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %15
+         %21 = OpFunctionParameter %17
+         %23 = OpLabel
+         %53 = OpVariable %7 Function
+         %54 = OpVariable %7 Function
+         %41 = OpLoad %6 %19
+         %44 = OpAccessChain %7 %21 %43
+         %45 = OpLoad %6 %44
+         %46 = OpFAdd %6 %41 %45
+         %47 = OpLoad %14 %20
+         %48 = OpConvertSToF %6 %47
+         %50 = OpAccessChain %7 %21 %49
+         %51 = OpLoad %6 %50
+         %52 = OpFAdd %6 %48 %51
+               OpStore %53 %46
+               OpStore %54 %52
+         %55 = OpFunctionCall %8 %12 %53 %54
+               OpReturnValue %55
+               OpFunctionEnd
+         %28 = OpFunction %24 None %25
+         %26 = OpFunctionParameter %15
+         %27 = OpFunctionParameter %7
+         %29 = OpLabel
+         %58 = OpLoad %14 %26
+         %59 = OpConvertSToF %6 %58
+         %60 = OpLoad %6 %27
+         %61 = OpFOrdLessThan %24 %59 %60
+               OpReturnValue %61
+               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;
+
+  // Can't permute main function
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 0, {}).IsApplicable(
+      context.get(), fact_manager));
+
+  // Can't permute invalid instruction
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(101, 0, {})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Permutation has too many values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {2, 1, 0, 3})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Permutation has too few values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {0, 1})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Permutation has invalid values
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {3, 1, 0})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Type id is not an OpTypeFunction instruction
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 42, {2, 1, 0})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Type id has incorrect number of operands
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 9, {2, 1, 0})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // OpTypeFunction has operands out of order
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 18, {2, 1, 0})
+                   .IsApplicable(context.get(), fact_manager));
+
+  // Successful transformations
+  {
+    // Function has two operands of the same type:
+    // initial OpTypeFunction should be enough
+    TransformationPermuteFunctionParameters transformation(12, 9, {1, 0});
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+  {
+    TransformationPermuteFunctionParameters transformation(28, 105, {1, 0});
+    ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+    transformation.Apply(context.get(), &fact_manager);
+    ASSERT_TRUE(IsValid(env, context.get()));
+  }
+
+  std::string after_transformation = R"(
+    OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main" %72 %74
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %12 "g(f1;f1;"
+               OpName %10 "x"
+               OpName %11 "y"
+               OpName %22 "f(f1;i1;vf2;"
+               OpName %19 "x"
+               OpName %20 "y"
+               OpName %21 "z"
+               OpName %28 "cond(i1;f1;"
+               OpName %26 "a"
+               OpName %27 "b"
+               OpName %53 "param"
+               OpName %54 "param"
+               OpName %66 "param"
+               OpName %67 "param"
+               OpName %72 "color"
+               OpName %74 "gl_FragCoord"
+               OpName %75 "param"
+               OpName %79 "param"
+               OpName %85 "param"
+               OpName %86 "param"
+               OpName %91 "param"
+               OpName %92 "param"
+               OpName %93 "param"
+               OpName %99 "param"
+               OpName %100 "param"
+               OpName %101 "param"
+               OpDecorate %20 RelaxedPrecision
+               OpDecorate %26 RelaxedPrecision
+               OpDecorate %47 RelaxedPrecision
+               OpDecorate %58 RelaxedPrecision
+               OpDecorate %72 Location 0
+               OpDecorate %74 BuiltIn FragCoord
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFloat 32
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeVector %6 4
+          %9 = OpTypeFunction %8 %7 %7
+         %14 = OpTypeInt 32 1
+         %15 = OpTypePointer Function %14
+         %16 = OpTypeVector %6 2
+         %17 = OpTypePointer Function %16
+         %18 = OpTypeFunction %8 %7 %15 %17
+         %24 = OpTypeBool
+         %25 = OpTypeFunction %24 %15 %7
+         %105 = OpTypeFunction %24 %7 %15    ; predefined type for %28
+         %31 = OpConstant %6 255
+         %33 = OpConstant %6 0
+         %34 = OpConstant %6 1
+         %42 = OpTypeInt 32 0
+         %43 = OpConstant %42 0
+         %49 = OpConstant %42 1
+         %64 = OpConstant %14 4
+         %65 = OpConstant %6 5
+         %71 = OpTypePointer Output %8
+         %72 = OpVariable %71 Output
+         %73 = OpTypePointer Input %8
+         %74 = OpVariable %73 Input
+         %76 = OpTypePointer Input %6
+         %84 = OpConstant %14 5
+         %90 = OpConstant %6 3
+         %98 = OpConstant %6 4
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %66 = OpVariable %15 Function
+         %67 = OpVariable %7 Function
+         %75 = OpVariable %7 Function
+         %79 = OpVariable %7 Function
+         %85 = OpVariable %15 Function
+         %86 = OpVariable %7 Function
+         %91 = OpVariable %7 Function
+         %92 = OpVariable %15 Function
+         %93 = OpVariable %17 Function
+         %99 = OpVariable %7 Function
+        %100 = OpVariable %15 Function
+        %101 = OpVariable %17 Function
+               OpStore %66 %64
+               OpStore %67 %65
+         %68 = OpFunctionCall %24 %28 %67 %66
+               OpSelectionMerge %70 None
+               OpBranchConditional %68 %69 %83
+         %69 = OpLabel
+         %77 = OpAccessChain %76 %74 %43
+         %78 = OpLoad %6 %77
+               OpStore %75 %78
+         %80 = OpAccessChain %76 %74 %49
+         %81 = OpLoad %6 %80
+               OpStore %79 %81
+         %82 = OpFunctionCall %8 %12 %79 %75
+               OpStore %72 %82
+               OpBranch %70
+         %83 = OpLabel
+               OpStore %85 %84
+               OpStore %86 %65
+         %87 = OpFunctionCall %24 %28 %86 %85
+               OpSelectionMerge %89 None
+               OpBranchConditional %87 %88 %97
+         %88 = OpLabel
+               OpStore %91 %90
+               OpStore %92 %64
+         %94 = OpLoad %8 %74
+         %95 = OpVectorShuffle %16 %94 %94 0 1
+               OpStore %93 %95
+         %96 = OpFunctionCall %8 %22 %91 %92 %93
+               OpStore %72 %96
+               OpBranch %89
+         %97 = OpLabel
+               OpStore %99 %98
+               OpStore %100 %84
+        %102 = OpLoad %8 %74
+        %103 = OpVectorShuffle %16 %102 %102 0 1
+               OpStore %101 %103
+        %104 = OpFunctionCall %8 %22 %99 %100 %101
+               OpStore %72 %104
+               OpBranch %89
+         %89 = OpLabel
+               OpBranch %70
+         %70 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %9
+         %11 = OpFunctionParameter %7
+         %10 = OpFunctionParameter %7
+         %13 = OpLabel
+         %30 = OpLoad %6 %10
+         %32 = OpFDiv %6 %30 %31
+         %35 = OpLoad %6 %11
+         %36 = OpFDiv %6 %35 %31
+         %37 = OpFSub %6 %34 %36
+         %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+               OpReturnValue %38
+               OpFunctionEnd
+         %22 = OpFunction %8 None %18
+         %19 = OpFunctionParameter %7
+         %20 = OpFunctionParameter %15
+         %21 = OpFunctionParameter %17
+         %23 = OpLabel
+         %53 = OpVariable %7 Function
+         %54 = OpVariable %7 Function
+         %41 = OpLoad %6 %19
+         %44 = OpAccessChain %7 %21 %43
+         %45 = OpLoad %6 %44
+         %46 = OpFAdd %6 %41 %45
+         %47 = OpLoad %14 %20
+         %48 = OpConvertSToF %6 %47
+         %50 = OpAccessChain %7 %21 %49
+         %51 = OpLoad %6 %50
+         %52 = OpFAdd %6 %48 %51
+               OpStore %53 %46
+               OpStore %54 %52
+         %55 = OpFunctionCall %8 %12 %54 %53
+               OpReturnValue %55
+               OpFunctionEnd
+         %28 = OpFunction %24 None %105
+         %27 = OpFunctionParameter %7
+         %26 = OpFunctionParameter %15
+         %29 = OpLabel
+         %58 = OpLoad %14 %26
+         %59 = OpConvertSToF %6 %58
+         %60 = OpLoad %6 %27
+         %61 = OpFOrdLessThan %24 %59 %60
+               OpReturnValue %61
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 1e8faf0..b228300 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -33,7 +33,8 @@
            'Google LLC',
            'Pierre Moreau',
            'Samsung Inc',
-           'André Perez Maselco']
+           'André Perez Maselco',
+           'Vasyl Teliman']
 CURRENT_YEAR='2020'
 
 YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2017-2019|2018|2019|2020)'