spirv-fuzz: Add one parameter at a time (#3469)

Fixes #3467.
Fixes #3468.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 8893d3e..d147408 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -95,7 +95,7 @@
         transformation_add_global_variable.h
         transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
-        transformation_add_parameters.h
+        transformation_add_parameter.h
         transformation_add_spec_constant_op.h
         transformation_add_type_array.h
         transformation_add_type_boolean.h
@@ -202,7 +202,7 @@
         transformation_add_global_variable.cpp
         transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
-        transformation_add_parameters.cpp
+        transformation_add_parameter.cpp
         transformation_add_spec_constant_op.cpp
         transformation_add_type_array.cpp
         transformation_add_type_boolean.cpp
diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp
index c931302..9273452 100644
--- a/source/fuzz/fuzzer_pass_add_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_add_parameters.cpp
@@ -17,7 +17,7 @@
 #include "source/fuzz/fuzzer_context.h"
 #include "source/fuzz/fuzzer_util.h"
 #include "source/fuzz/instruction_descriptor.h"
-#include "source/fuzz/transformation_add_parameters.h"
+#include "source/fuzz/transformation_add_parameter.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -57,50 +57,17 @@
       continue;
     }
 
-    const auto* type_inst =
-        fuzzerutil::GetFunctionType(GetIRContext(), &function);
-    assert(type_inst);
-
-    // -1 because we don't take return type into account.
-    auto num_old_parameters = type_inst->NumInOperands() - 1;
     auto num_new_parameters =
         GetFuzzerContext()->GetRandomNumberOfNewParameters(
             GetNumberOfParameters(function));
-
-    std::vector<uint32_t> all_types(num_old_parameters);
-    std::vector<uint32_t> new_types(num_new_parameters);
-    std::vector<uint32_t> parameter_ids(num_new_parameters);
-    std::vector<uint32_t> constant_ids(num_new_parameters);
-
-    // Get type ids for old parameters.
-    for (uint32_t i = 0; i < num_old_parameters; ++i) {
-      // +1 since we don't take return type into account.
-      all_types[i] = type_inst->GetSingleWordInOperand(i + 1);
-    }
-
     for (uint32_t i = 0; i < num_new_parameters; ++i) {
-      // Get type ids for new parameters.
-      new_types[i] =
-          type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)];
-
-      // Create constants to initialize new parameters from.
-      constant_ids[i] = FindOrCreateZeroConstant(new_types[i]);
+      ApplyTransformation(TransformationAddParameter(
+          function.result_id(), GetFuzzerContext()->GetFreshId(),
+          FindOrCreateZeroConstant(
+              type_candidates[GetFuzzerContext()->RandomIndex(
+                  type_candidates)]),
+          GetFuzzerContext()->GetFreshId()));
     }
-
-    // Append new parameters to the old ones.
-    all_types.insert(all_types.end(), new_types.begin(), new_types.end());
-
-    // Generate result ids for new parameters.
-    for (auto& id : parameter_ids) {
-      id = GetFuzzerContext()->GetFreshId();
-    }
-
-    auto result_type_id = type_inst->GetSingleWordInOperand(0);
-    ApplyTransformation(TransformationAddParameters(
-        function.result_id(),
-        FindOrCreateFunctionType(result_type_id, all_types),
-        std::move(new_types), std::move(parameter_ids),
-        std::move(constant_ids)));
   }
 }
 
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
index 57d9cab..e15aef6 100644
--- a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -61,20 +61,9 @@
     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));
+        function_id, GetFuzzerContext()->GetFreshId(), permutation));
   }
 }
 
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 79881fc..7b137cf 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -12,11 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/fuzzer_util.h"
+
 #include <algorithm>
 #include <unordered_set>
 
-#include "source/fuzz/fuzzer_util.h"
-
 #include "source/opt/build_module.h"
 
 namespace spvtools {
@@ -590,6 +590,8 @@
                        uint32_t type_id, SpvStorageClass storage_class,
                        uint32_t initializer_id) {
   // Check various preconditions.
+  assert(result_id != 0 && "Result id can't be 0");
+
   assert((storage_class == SpvStorageClassPrivate ||
           storage_class == SpvStorageClassWorkgroup) &&
          "Variable's storage class must be either Private or Workgroup");
@@ -631,6 +633,8 @@
                       uint32_t type_id, uint32_t function_id,
                       uint32_t initializer_id) {
   // Check various preconditions.
+  assert(result_id != 0 && "Result id can't be 0");
+
   auto* type_inst = context->get_def_use_mgr()->GetDef(type_id);
   (void)type_inst;  // Variable becomes unused in release mode.
   assert(type_inst && type_inst->opcode() == SpvOpTypePointer &&
@@ -687,6 +691,44 @@
   return result;
 }
 
+void AddFunctionType(opt::IRContext* ir_context, uint32_t result_id,
+                     const std::vector<uint32_t>& type_ids) {
+  assert(result_id != 0 && "Result id can't be 0");
+  assert(!type_ids.empty() &&
+         "OpTypeFunction always has at least one operand - function's return "
+         "type");
+  assert(IsNonFunctionTypeId(ir_context, type_ids[0]) &&
+         "Return type must not be a function");
+
+  for (size_t i = 1; i < type_ids.size(); ++i) {
+    const auto* param_type = ir_context->get_type_mgr()->GetType(type_ids[i]);
+    (void)param_type;  // Make compiler happy in release mode.
+    assert(param_type && !param_type->AsVoid() && !param_type->AsFunction() &&
+           "Function parameter can't have a function or void type");
+  }
+
+  opt::Instruction::OperandList operands;
+  operands.reserve(type_ids.size());
+  for (auto id : type_ids) {
+    operands.push_back({SPV_OPERAND_TYPE_ID, {id}});
+  }
+
+  ir_context->AddType(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpTypeFunction, 0, result_id, std::move(operands)));
+
+  UpdateModuleIdBound(ir_context, result_id);
+}
+
+uint32_t FindOrCreateFunctionType(opt::IRContext* ir_context,
+                                  uint32_t result_id,
+                                  const std::vector<uint32_t>& type_ids) {
+  if (auto existing_id = FindFunctionType(ir_context, type_ids)) {
+    return existing_id;
+  }
+  AddFunctionType(ir_context, result_id, type_ids);
+  return result_id;
+}
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index 0cc5d49..7928df0 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -266,6 +266,24 @@
 std::vector<opt::Instruction*> GetParameters(opt::IRContext* ir_context,
                                              uint32_t function_id);
 
+// Creates new OpTypeFunction instruction in the module. |type_ids| may not be
+// empty. It may not contain result ids of OpTypeFunction instructions.
+// |type_ids[i]| may not be a result id of OpTypeVoid instruction for |i >= 1|.
+// |result_id| may not equal to 0. Updates module's id bound to accommodate for
+// |result_id|.
+void AddFunctionType(opt::IRContext* ir_context, uint32_t result_id,
+                     const std::vector<uint32_t>& type_ids);
+
+// Returns a result id of an OpTypeFunction instruction in the module. Creates a
+// new instruction if required and returns |result_id|. type_ids| may not be
+// empty. It may not contain result ids of OpTypeFunction instructions.
+// |type_ids[i]| may not be a result id of OpTypeVoid instruction for |i >= 1|.
+// |result_id| must not be equal to 0. Updates module's id bound to accommodate
+// for |result_id|.
+uint32_t FindOrCreateFunctionType(opt::IRContext* ir_context,
+                                  uint32_t result_id,
+                                  const std::vector<uint32_t>& type_ids);
+
 }  // namespace fuzzerutil
 
 }  // namespace fuzz
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 7b8adc0..7e0db3e 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -380,7 +380,7 @@
     TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 49;
     TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50;
     TransformationPermutePhiOperands permute_phi_operands = 51;
-    TransformationAddParameters add_parameters = 52;
+    TransformationAddParameter add_parameter = 52;
     // Add additional option using the next available number.
   }
 }
@@ -624,24 +624,25 @@
 
 }
 
-message TransformationAddParameters {
+message TransformationAddParameter {
 
-  // Adds new parameters into the function.
+  // Adds a new parameter into the function.
 
   // Result id of the function to add parameters to.
   uint32 function_id = 1;
 
-  // New type of the function.
-  uint32 new_type_id = 2;
+  // Fresh id for a new parameter.
+  uint32 parameter_fresh_id = 2;
 
-  // Type ids of parameters to add to the function.
-  repeated uint32 new_parameter_type = 3;
+  // Result id of the instruction, used to initializer new parameter
+  // in function calls. Type id of that instruction is the type id of the parameter.
+  // It may not be OpTypeVoid.
+  uint32 initializer_id = 3;
 
-  // Result ids for new parameters.
-  repeated uint32 new_parameter_id = 4;
-
-  // Constants to initialize new parameters from.
-  repeated uint32 constant_id = 5;
+  // A fresh id for a new function type. This might not be used
+  // if a required function type already exists or if we can change
+  // the old function type.
+  uint32 function_type_fresh_id = 4;
 
 }
 
@@ -1011,12 +1012,10 @@
   // 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;
+  // Fresh id for a new type of the function. This might not be used
+  // if a required function type already exists or if we can change
+  // the old function type.
+  uint32 function_type_fresh_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.
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 3177de0..ac16a9f 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,7 +30,7 @@
 #include "source/fuzz/transformation_add_global_variable.h"
 #include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
-#include "source/fuzz/transformation_add_parameters.h"
+#include "source/fuzz/transformation_add_parameter.h"
 #include "source/fuzz/transformation_add_spec_constant_op.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
@@ -115,8 +115,8 @@
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
           message.add_no_contraction_decoration());
-    case protobufs::Transformation::TransformationCase::kAddParameters:
-      return MakeUnique<TransformationAddParameters>(message.add_parameters());
+    case protobufs::Transformation::TransformationCase::kAddParameter:
+      return MakeUnique<TransformationAddParameter>(message.add_parameter());
     case protobufs::Transformation::TransformationCase::kAddSpecConstantOp:
       return MakeUnique<TransformationAddSpecConstantOp>(
           message.add_spec_constant_op());
diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp
new file mode 100644
index 0000000..c6ddc1c
--- /dev/null
+++ b/source/fuzz/transformation_add_parameter.cpp
@@ -0,0 +1,135 @@
+// 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_add_parameter.h"
+
+#include <source/spirv_constant.h>
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddParameter::TransformationAddParameter(
+    const protobufs::TransformationAddParameter& message)
+    : message_(message) {}
+
+TransformationAddParameter::TransformationAddParameter(
+    uint32_t function_id, uint32_t parameter_fresh_id, uint32_t initializer_id,
+    uint32_t function_type_fresh_id) {
+  message_.set_function_id(function_id);
+  message_.set_parameter_fresh_id(parameter_fresh_id);
+  message_.set_initializer_id(initializer_id);
+  message_.set_function_type_fresh_id(function_type_fresh_id);
+}
+
+bool TransformationAddParameter::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // Check that function exists
+  const auto* function =
+      fuzzerutil::FindFunction(ir_context, message_.function_id());
+  if (!function ||
+      fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
+    return false;
+  }
+
+  // Check that |initializer_id| is valid.
+  const auto* initializer_inst =
+      ir_context->get_def_use_mgr()->GetDef(message_.initializer_id());
+
+  if (!initializer_inst) {
+    return false;
+  }
+
+  // Check that initializer's type is valid.
+  const auto* initializer_type =
+      ir_context->get_type_mgr()->GetType(initializer_inst->type_id());
+
+  if (!initializer_type || initializer_type->AsVoid()) {
+    return false;
+  }
+
+  return fuzzerutil::IsFreshId(ir_context, message_.parameter_fresh_id()) &&
+         fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id()) &&
+         message_.parameter_fresh_id() != message_.function_type_fresh_id();
+}
+
+void TransformationAddParameter::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // Find the function that will be transformed
+  auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
+  assert(function && "Can't find the function");
+
+  auto parameter_type_id =
+      fuzzerutil::GetTypeId(ir_context, message_.initializer_id());
+
+  // Add new parameters to the function.
+  function->AddParameter(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpFunctionParameter, parameter_type_id,
+      message_.parameter_fresh_id(), opt::Instruction::OperandList()));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id());
+
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
+  //  Add an PointeeValueIsIrrelevant fact if the parameter is a pointer.
+
+  // Fix all OpFunctionCall instructions.
+  ir_context->get_def_use_mgr()->ForEachUser(
+      &function->DefInst(), [this](opt::Instruction* call) {
+        if (call->opcode() != SpvOpFunctionCall ||
+            call->GetSingleWordInOperand(0) != message_.function_id()) {
+          return;
+        }
+
+        // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
+        //  it would be good to mark this usage of |id| as irrelevant, so that
+        //  we can perform some interesting transformations on it later.
+        call->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
+      });
+
+  auto* old_function_type = fuzzerutil::GetFunctionType(ir_context, function);
+  assert(old_function_type && "Function must have a valid type");
+
+  if (ir_context->get_def_use_mgr()->NumUsers(old_function_type) == 1) {
+    // Adjust existing function type if it is used only by this function.
+    old_function_type->AddOperand({SPV_OPERAND_TYPE_ID, {parameter_type_id}});
+  } else {
+    // Otherwise, either create a new type or use an existing one.
+    std::vector<uint32_t> type_ids;
+    type_ids.reserve(old_function_type->NumInOperands() + 1);
+
+    for (uint32_t i = 0, n = old_function_type->NumInOperands(); i < n; ++i) {
+      type_ids.push_back(old_function_type->GetSingleWordInOperand(i));
+    }
+
+    type_ids.push_back(parameter_type_id);
+
+    function->DefInst().SetInOperand(
+        1, {fuzzerutil::FindOrCreateFunctionType(
+               ir_context, message_.function_type_fresh_id(), type_ids)});
+  }
+
+  // Make sure our changes are analyzed.
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationAddParameter::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_parameter() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_parameter.h b/source/fuzz/transformation_add_parameter.h
new file mode 100644
index 0000000..42b5fbb
--- /dev/null
+++ b/source/fuzz/transformation_add_parameter.h
@@ -0,0 +1,62 @@
+// 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_ADD_PARAMETER_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETER_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 TransformationAddParameter : public Transformation {
+ public:
+  explicit TransformationAddParameter(
+      const protobufs::TransformationAddParameter& message);
+
+  TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id,
+                             uint32_t initializer_id,
+                             uint32_t function_type_fresh_id);
+
+  // - |function_id| must be a valid result id of some non-entry-point function
+  //   in the module.
+  // - |initializer_id| must be a valid result id of some instruction in the
+  //   module. Instruction's type may not be OpTypeVoid.
+  // - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are
+  //   not equal.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // - Creates a new OpFunctionParameter instruction with result id
+  //   |parameter_fresh_id| for the function with |function_id|.
+  // - Adjusts function's type to include a new parameter.
+  // - Adds |initializer_id| as a new operand to every OpFunctionCall
+  //   instruction that calls the function.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddParameter message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETER_H_
diff --git a/source/fuzz/transformation_add_parameters.cpp b/source/fuzz/transformation_add_parameters.cpp
deleted file mode 100644
index 28af854..0000000
--- a/source/fuzz/transformation_add_parameters.cpp
+++ /dev/null
@@ -1,201 +0,0 @@
-// 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_add_parameters.h"
-
-#include <source/spirv_constant.h>
-
-#include "source/fuzz/fuzzer_util.h"
-
-namespace spvtools {
-namespace fuzz {
-
-TransformationAddParameters::TransformationAddParameters(
-    const protobufs::TransformationAddParameters& message)
-    : message_(message) {}
-
-TransformationAddParameters::TransformationAddParameters(
-    uint32_t function_id, uint32_t new_type_id,
-    const std::vector<uint32_t>& new_parameter_type,
-    const std::vector<uint32_t>& new_parameter_id,
-    const std::vector<uint32_t>& constant_id) {
-  message_.set_function_id(function_id);
-  message_.set_new_type_id(new_type_id);
-
-  for (auto id : new_parameter_type) {
-    message_.add_new_parameter_type(id);
-  }
-
-  for (auto id : new_parameter_id) {
-    message_.add_new_parameter_id(id);
-  }
-
-  for (auto id : constant_id) {
-    message_.add_constant_id(id);
-  }
-}
-
-bool TransformationAddParameters::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
-  // Check that function exists
-  const auto* function =
-      fuzzerutil::FindFunction(ir_context, message_.function_id());
-  if (!function ||
-      fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
-    return false;
-  }
-
-  // Validate new parameters.
-  const auto& new_type_ids = message_.new_parameter_type();
-  const auto& new_parameter_ids = message_.new_parameter_id();
-  const auto& constant_ids = message_.constant_id();
-
-  // All three vectors must have the same size.
-  if (new_type_ids.size() != new_parameter_ids.size() ||
-      new_type_ids.size() != constant_ids.size()) {
-    return false;
-  }
-
-  // Vectors must have at least one component.
-  if (new_type_ids.empty()) {
-    return false;
-  }
-
-  // Check that type ids exist in the module and are not OpTypeVoid.
-  for (auto id : new_type_ids) {
-    const auto* type = ir_context->get_type_mgr()->GetType(id);
-    if (!type || type->AsVoid()) {
-      return false;
-    }
-  }
-
-  // Check that all parameter ids are fresh.
-  for (auto id : new_parameter_ids) {
-    if (!fuzzerutil::IsFreshId(ir_context, id)) {
-      return false;
-    }
-  }
-
-  // Check that constants exist and have valid type.
-  for (int i = 0, n = constant_ids.size(); i < n; ++i) {
-    const auto* inst = ir_context->get_def_use_mgr()->GetDef(constant_ids[i]);
-    if (!inst || inst->type_id() != new_type_ids[i]) {
-      return false;
-    }
-  }
-
-  // Validate new function type.
-  const auto* old_type_inst = fuzzerutil::GetFunctionType(ir_context, function);
-  const auto* new_type_inst =
-      ir_context->get_def_use_mgr()->GetDef(message_.new_type_id());
-
-  // Both types must exist.
-  assert(old_type_inst && old_type_inst->opcode() == SpvOpTypeFunction);
-  if (!new_type_inst || new_type_inst->opcode() != SpvOpTypeFunction) {
-    return false;
-  }
-
-  auto num_old_parameters = old_type_inst->NumInOperands();
-  auto num_new_parameters = new_type_ids.size();
-
-  // New function type has been added to the module which means that it's valid.
-  // Thus, we don't need to check whether the limit on the number of arguments
-  // is satisfied.
-
-  // New type = old type + new parameters.
-  if (new_type_inst->NumInOperands() !=
-      num_old_parameters + num_new_parameters) {
-    return false;
-  }
-
-  // Check that old parameters and the return type are preserved.
-  for (uint32_t i = 0; i < num_old_parameters; ++i) {
-    if (new_type_inst->GetSingleWordInOperand(i) !=
-        old_type_inst->GetSingleWordInOperand(i)) {
-      return false;
-    }
-  }
-
-  // Check that new parameters have been appended.
-  for (int i = 0; i < num_new_parameters; ++i) {
-    if (new_type_inst->GetSingleWordInOperand(i + num_old_parameters) !=
-        new_type_ids[i]) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-void TransformationAddParameters::Apply(
-    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  // Retrieve all data from the message
-  auto function_id = message_.function_id();
-  const auto& new_parameter_type = message_.new_parameter_type();
-  const auto& new_parameter_id = message_.new_parameter_id();
-  const auto& constant_id = message_.constant_id();
-
-  // Find the function that will be transformed
-  auto* function = fuzzerutil::FindFunction(ir_context, function_id);
-  assert(function && "Can't find the function");
-
-  // Change function's type
-  function->DefInst().SetInOperand(1, {message_.new_type_id()});
-
-  // Add new parameters to the function.
-  for (int i = 0, n = new_parameter_id.size(); i < n; ++i) {
-    function->AddParameter(MakeUnique<opt::Instruction>(
-        ir_context, SpvOpFunctionParameter, new_parameter_type[i],
-        new_parameter_id[i], opt::Instruction::OperandList()));
-
-    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
-    //  Add an PointeeValueIsIrrelevant fact if the parameter is a pointer.
-  }
-
-  // Fix all OpFunctionCall instructions.
-  ir_context->get_def_use_mgr()->ForEachUser(
-      &function->DefInst(),
-      [function_id, &constant_id](opt::Instruction* call) {
-        if (call->opcode() != SpvOpFunctionCall ||
-            call->GetSingleWordInOperand(0) != function_id) {
-          return;
-        }
-
-        for (auto id : constant_id) {
-          // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3177):
-          //  it would be good to mark this usage of |id| as irrelevant, so that
-          //  we can perform some interesting transformations on it later.
-          call->AddOperand({SPV_OPERAND_TYPE_ID, {id}});
-        }
-      });
-
-  // Update module's id bound. We can safely dereference the result of
-  // max_element since |new_parameter_id| is guaranteed to have elements.
-  fuzzerutil::UpdateModuleIdBound(
-      ir_context,
-      *std::max_element(new_parameter_id.begin(), new_parameter_id.end()));
-
-  // Make sure our changes are analyzed.
-  ir_context->InvalidateAnalysesExceptFor(
-      opt::IRContext::Analysis::kAnalysisNone);
-}
-
-protobufs::Transformation TransformationAddParameters::ToMessage() const {
-  protobufs::Transformation result;
-  *result.mutable_add_parameters() = message_;
-  return result;
-}
-
-}  // namespace fuzz
-}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_parameters.h b/source/fuzz/transformation_add_parameters.h
deleted file mode 100644
index 681195c..0000000
--- a/source/fuzz/transformation_add_parameters.h
+++ /dev/null
@@ -1,70 +0,0 @@
-// 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_ADD_PARAMETERS_H_
-#define SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETERS_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 TransformationAddParameters : public Transformation {
- public:
-  explicit TransformationAddParameters(
-      const protobufs::TransformationAddParameters& message);
-
-  TransformationAddParameters(uint32_t function_id, uint32_t new_type_id,
-                              const std::vector<uint32_t>& new_parameter_type,
-                              const std::vector<uint32_t>& new_parameter_id,
-                              const std::vector<uint32_t>& constant_id);
-
-  // - |function_id| must be a valid result id of some non-entry-point function
-  //   in the module.
-  // - |new_type_id| must be a result id of OpTypeFunction instruction.
-  // - New type of the function must have the same return type. New function
-  //   parameters must be appended to the old ones.
-  // - |new_parameter_type| contains result ids of some OpType* instructions in
-  //   the module. It may not contain result ids of OpTypeVoid.
-  // - |new_parameter_id| contains fresh ids.
-  // - |constant_id| contains result ids used to initialize new parameters. Type
-  //   ids of these instructions must be the same as |new_parameter_type| (i.e.
-  //   |new_parameter_type[i] == GetDef(constant_id[i])->type_id()|).
-  // - |new_parameter_id|, |new_parameter_type| and |constant_id| should all
-  //   have the same size and may not be empty.
-  bool IsApplicable(
-      opt::IRContext* ir_context,
-      const TransformationContext& transformation_context) const override;
-
-  // - Creates new OpFunctionParameter instructions for the function with
-  //   |function_id|.
-  // - Changes type of the function to |new_type_id|.
-  // - Adds ids from |constant_id| to every OpFunctionCall instruction that
-  //   calls the function.
-  void Apply(opt::IRContext* ir_context,
-             TransformationContext* transformation_context) const override;
-
-  protobufs::Transformation ToMessage() const override;
-
- private:
-  protobufs::TransformationAddParameters message_;
-};
-
-}  // namespace fuzz
-}  // namespace spvtools
-
-#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_PARAMETERS_H_
diff --git a/source/fuzz/transformation_add_type_function.cpp b/source/fuzz/transformation_add_type_function.cpp
index 991a28b..c878025 100644
--- a/source/fuzz/transformation_add_type_function.cpp
+++ b/source/fuzz/transformation_add_type_function.cpp
@@ -55,48 +55,19 @@
   // exactly the same return and argument type ids.  (Note that the type manager
   // does not allow us to check this, as it does not distinguish between
   // function types with different but isomorphic pointer argument types.)
-  for (auto& inst : ir_context->module()->types_values()) {
-    if (inst.opcode() != SpvOpTypeFunction) {
-      // Consider only OpTypeFunction instructions.
-      continue;
-    }
-    if (inst.GetSingleWordInOperand(0) != message_.return_type_id()) {
-      // Different return types - cannot be the same.
-      continue;
-    }
-    if (inst.NumInOperands() !=
-        1 + static_cast<uint32_t>(message_.argument_type_id().size())) {
-      // Different numbers of arguments - cannot be the same.
-      continue;
-    }
-    bool found_argument_mismatch = false;
-    for (uint32_t index = 1; index < inst.NumInOperands(); index++) {
-      if (message_.argument_type_id(index - 1) !=
-          inst.GetSingleWordInOperand(index)) {
-        // Argument mismatch - cannot be the same.
-        found_argument_mismatch = true;
-        break;
-      }
-    }
-    if (found_argument_mismatch) {
-      continue;
-    }
-    // Everything matches - the type is already declared.
-    return false;
-  }
-  return true;
+  std::vector<uint32_t> type_ids = {message_.return_type_id()};
+  type_ids.insert(type_ids.end(), message_.argument_type_id().begin(),
+                  message_.argument_type_id().end());
+  return fuzzerutil::FindFunctionType(ir_context, type_ids) == 0;
 }
 
 void TransformationAddTypeFunction::Apply(
     opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
-  opt::Instruction::OperandList in_operands;
-  in_operands.push_back({SPV_OPERAND_TYPE_ID, {message_.return_type_id()}});
-  for (auto argument_type_id : message_.argument_type_id()) {
-    in_operands.push_back({SPV_OPERAND_TYPE_ID, {argument_type_id}});
-  }
-  ir_context->module()->AddType(MakeUnique<opt::Instruction>(
-      ir_context, SpvOpTypeFunction, 0, message_.fresh_id(), in_operands));
-  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+  std::vector<uint32_t> type_ids = {message_.return_type_id()};
+  type_ids.insert(type_ids.end(), message_.argument_type_id().begin(),
+                  message_.argument_type_id().end());
+
+  fuzzerutil::AddFunctionType(ir_context, message_.fresh_id(), type_ids);
   // We have added an instruction to the module, so need to be careful about the
   // validity of existing analyses.
   ir_context->InvalidateAnalysesExceptFor(
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
index 4b9561c..5f06605 100644
--- a/source/fuzz/transformation_permute_function_parameters.cpp
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
 #include <vector>
 
 #include "source/fuzz/fuzzer_util.h"
-#include "source/fuzz/transformation_permute_function_parameters.h"
 
 namespace spvtools {
 namespace fuzz {
@@ -28,10 +29,10 @@
 
 TransformationPermuteFunctionParameters::
     TransformationPermuteFunctionParameters(
-        uint32_t function_id, uint32_t new_type_id,
+        uint32_t function_id, uint32_t function_type_fresh_id,
         const std::vector<uint32_t>& permutation) {
   message_.set_function_id(function_id);
-  message_.set_new_type_id(new_type_id);
+  message_.set_function_type_fresh_id(function_type_fresh_id);
 
   for (auto index : permutation) {
     message_.add_permutation(index);
@@ -77,49 +78,46 @@
     return false;
   }
 
-  // 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 = ir_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;
+  return fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id());
 }
 
 void TransformationPermuteFunctionParameters::Apply(
     opt::IRContext* ir_context, TransformationContext* /*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(ir_context, function_id);
+  auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
   assert(function && "Can't find the function");
 
+  auto* old_function_type_inst =
+      fuzzerutil::GetFunctionType(ir_context, function);
+  assert(old_function_type_inst && "Function must have a valid type");
+
   // Change function's type
-  function->DefInst().SetInOperand(1, {new_type_id});
+  if (ir_context->get_def_use_mgr()->NumUsers(old_function_type_inst) == 1) {
+    // If only the current function uses |old_function_type_inst| - change it
+    // in-place.
+    opt::Instruction::OperandList permuted_operands = {
+        std::move(old_function_type_inst->GetInOperand(0))};
+    for (auto index : message_.permutation()) {
+      // +1 since the first operand to OpTypeFunction is a return type.
+      permuted_operands.push_back(
+          std::move(old_function_type_inst->GetInOperand(index + 1)));
+    }
+
+    old_function_type_inst->SetInOperands(std::move(permuted_operands));
+  } else {
+    // Either use an existing type or create a new one.
+    std::vector<uint32_t> type_ids = {
+        old_function_type_inst->GetSingleWordInOperand(0)};
+    for (auto index : message_.permutation()) {
+      // +1 since the first operand to OpTypeFunction is a return type.
+      type_ids.push_back(
+          old_function_type_inst->GetSingleWordInOperand(index + 1));
+    }
+
+    function->DefInst().SetInOperand(
+        1, {fuzzerutil::FindOrCreateFunctionType(
+               ir_context, message_.function_type_fresh_id(), type_ids)});
+  }
 
   // Adjust OpFunctionParameter instructions
 
@@ -133,7 +131,7 @@
 
   // Permute parameters' ids and types
   std::vector<uint32_t> permuted_param_id, permuted_param_type;
-  for (auto index : permutation) {
+  for (auto index : message_.permutation()) {
     permuted_param_id.push_back(param_id[index]);
     permuted_param_type.push_back(param_type[index]);
   }
@@ -149,10 +147,9 @@
 
   // Fix all OpFunctionCall instructions
   ir_context->get_def_use_mgr()->ForEachUser(
-      &function->DefInst(),
-      [function_id, &permutation](opt::Instruction* call) {
+      &function->DefInst(), [this](opt::Instruction* call) {
         if (call->opcode() != SpvOpFunctionCall ||
-            call->GetSingleWordInOperand(0) != function_id) {
+            call->GetSingleWordInOperand(0) != message_.function_id()) {
           return;
         }
 
@@ -160,7 +157,7 @@
             call->GetInOperand(0)  // Function id
         };
 
-        for (auto index : permutation) {
+        for (auto index : message_.permutation()) {
           // Take function id into account
           call_operands.push_back(call->GetInOperand(index + 1));
         }
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
index 994e4c2..8308051 100644
--- a/source/fuzz/transformation_permute_function_parameters.h
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -29,11 +29,11 @@
       const protobufs::TransformationPermuteFunctionParameters& message);
 
   TransformationPermuteFunctionParameters(
-      uint32_t function_id, uint32_t new_type_id,
+      uint32_t function_id, uint32_t function_type_fresh_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.
+  // - |function_type_fresh_id| is a fresh id.
   //   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
@@ -46,7 +46,7 @@
 
   // - 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|
+  // - Adjusts function's type to accommodate for permuted parameters.
   // - Calls to the function are adjusted accordingly
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 7d9c35c..57d770d 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -37,7 +37,7 @@
           transformation_add_global_variable_test.cpp
           transformation_add_local_variable_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
-          transformation_add_parameters_test.cpp
+          transformation_add_parameter_test.cpp
           transformation_add_type_array_test.cpp
           transformation_add_type_boolean_test.cpp
           transformation_add_type_float_test.cpp
diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp
new file mode 100644
index 0000000..b95f5dd
--- /dev/null
+++ b/test/fuzz/transformation_add_parameter_test.cpp
@@ -0,0 +1,126 @@
+// 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_add_parameter.h"
+
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddParameterTest, 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
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %7 = OpTypeBool
+         %11 = OpTypeInt 32 1
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFunction %7 %7
+          %8 = OpConstant %11 23
+         %12 = OpConstantTrue %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpFunctionCall %7 %9 %12
+               OpReturn
+               OpFunctionEnd
+          %9 = OpFunction %7 None %6
+         %14 = OpFunctionParameter %7
+         %10 = OpLabel
+               OpReturnValue %12
+               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);
+
+  // Can't modify entry point function.
+  ASSERT_FALSE(TransformationAddParameter(4, 15, 12, 16)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // There is no function with result id 29.
+  ASSERT_FALSE(TransformationAddParameter(29, 15, 8, 16)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Parameter id is not fresh.
+  ASSERT_FALSE(TransformationAddParameter(9, 14, 8, 16)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Function type id is not fresh.
+  ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 14)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Function type id and parameter type id are equal.
+  ASSERT_FALSE(TransformationAddParameter(9, 15, 8, 15)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Parameter's initializer doesn't exist.
+  ASSERT_FALSE(TransformationAddParameter(9, 15, 15, 16)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // Correct transformation.
+  TransformationAddParameter correct(9, 15, 8, 16);
+  ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
+  correct.Apply(context.get(), &transformation_context);
+
+  // The module remains valid.
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+          %2 = OpTypeVoid
+          %7 = OpTypeBool
+         %11 = OpTypeInt 32 1
+          %3 = OpTypeFunction %2
+          %6 = OpTypeFunction %7 %7 %11
+          %8 = OpConstant %11 23
+         %12 = OpConstantTrue %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %13 = OpFunctionCall %7 %9 %12 %8
+               OpReturn
+               OpFunctionEnd
+          %9 = OpFunction %7 None %6
+         %14 = OpFunctionParameter %7
+         %15 = OpFunctionParameter %11
+         %10 = OpLabel
+               OpReturnValue %12
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/test/fuzz/transformation_add_parameters_test.cpp b/test/fuzz/transformation_add_parameters_test.cpp
deleted file mode 100644
index 37c2e93..0000000
--- a/test/fuzz/transformation_add_parameters_test.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-// 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_add_parameters.h"
-#include "test/fuzz/fuzz_test_util.h"
-
-namespace spvtools {
-namespace fuzz {
-namespace {
-
-TEST(TransformationAddParametersTest, 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
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %7 = OpTypeBool
-         %11 = OpTypeInt 32 1
-          %3 = OpTypeFunction %2
-         %20 = OpTypeFunction %2 %7
-          %6 = OpTypeFunction %7 %7
-         %12 = OpTypeFunction %7 %7 %11
-         %13 = OpTypeFunction %7 %7 %7
-         %14 = OpTypeFunction %11 %7 %11
-         %15 = OpTypeFunction %7 %11 %11
-         %16 = OpTypeFunction %7 %7 %11 %11
-          %8 = OpConstant %11 23
-         %17 = OpConstantTrue %7
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-         %18 = OpFunctionCall %7 %9 %17
-               OpReturn
-               OpFunctionEnd
-          %9 = OpFunction %7 None %6
-         %19 = OpFunctionParameter %7
-         %10 = OpLabel
-               OpReturnValue %17
-               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);
-
-  // Can't modify entry point function.
-  ASSERT_FALSE(TransformationAddParameters(4, 6, {20}, {21}, {17})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // There is no function with result id 10.
-  ASSERT_FALSE(TransformationAddParameters(29, 12, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // There is no OpTypeFunction instruction with result id 21.
-  ASSERT_FALSE(TransformationAddParameters(9, 21, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Function type with id 6 has fewer parameters than required.
-  ASSERT_FALSE(TransformationAddParameters(9, 6, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Function type with id 16 has more parameters than required.
-  ASSERT_FALSE(TransformationAddParameters(9, 16, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // New function type is not OpTypeFunction instruction.
-  ASSERT_FALSE(TransformationAddParameters(9, 11, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Return type is invalid.
-  ASSERT_FALSE(TransformationAddParameters(9, 14, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Types of original parameters are invalid.
-  ASSERT_FALSE(TransformationAddParameters(9, 15, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Types of new parameters are invalid.
-  ASSERT_FALSE(TransformationAddParameters(9, 13, {11}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // OpTypeVoid can't be the type of function parameter.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {2}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Vectors, that describe parameters, have different sizes.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {}, {21}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Vectors cannot be empty.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {}, {}, {})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Parameters' ids are not fresh.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {20}, {8})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Constants for parameters don't exist.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {21})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Constants for parameters have invalid type.
-  ASSERT_FALSE(TransformationAddParameters(9, 12, {11}, {21}, {17})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // Correct transformation.
-  TransformationAddParameters correct(9, 12, {11}, {21}, {8});
-  ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
-  correct.Apply(context.get(), &transformation_context);
-
-  // The module remains valid.
-  ASSERT_TRUE(IsValid(env, context.get()));
-
-  std::string expected_shader = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main"
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-          %2 = OpTypeVoid
-          %7 = OpTypeBool
-         %11 = OpTypeInt 32 1
-          %3 = OpTypeFunction %2
-         %20 = OpTypeFunction %2 %7
-          %6 = OpTypeFunction %7 %7
-         %12 = OpTypeFunction %7 %7 %11
-         %13 = OpTypeFunction %7 %7 %7
-         %14 = OpTypeFunction %11 %7 %11
-         %15 = OpTypeFunction %7 %11 %11
-         %16 = OpTypeFunction %7 %7 %11 %11
-          %8 = OpConstant %11 23
-         %17 = OpConstantTrue %7
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-         %18 = OpFunctionCall %7 %9 %17 %8
-               OpReturn
-               OpFunctionEnd
-          %9 = OpFunction %7 None %12
-         %19 = OpFunctionParameter %7
-         %21 = OpFunctionParameter %11
-         %10 = OpLabel
-               OpReturnValue %17
-               OpFunctionEnd
-  )";
-
-  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
-}
-
-}  // namespace
-}  // namespace fuzz
-}  // namespace spvtools
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
index 13daff6..1443625 100644
--- a/test/fuzz/transformation_permute_function_parameters_test.cpp
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_permute_function_parameters.h"
+
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -73,7 +74,6 @@
          %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
@@ -205,56 +205,46 @@
                                                validator_options);
 
   // Can't permute main function
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 0, {}).IsApplicable(
-      context.get(), transformation_context));
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 105, {})
+                   .IsApplicable(context.get(), transformation_context));
 
   // Can't permute invalid instruction
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(101, 0, {})
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(101, 105, {})
                    .IsApplicable(context.get(), transformation_context));
 
   // Permutation has too many values
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {2, 1, 0, 3})
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 105, {2, 1, 0, 3})
                    .IsApplicable(context.get(), transformation_context));
 
   // Permutation has too few values
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {0, 1})
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 105, {0, 1})
                    .IsApplicable(context.get(), transformation_context));
 
   // Permutation has invalid values 1
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {3, 1, 0})
+  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 105, {3, 1, 0})
                    .IsApplicable(context.get(), transformation_context));
 
 #ifndef NDEBUG
   // Permutation has invalid values 2
-  ASSERT_DEATH(TransformationPermuteFunctionParameters(22, 0, {2, 2, 1})
+  ASSERT_DEATH(TransformationPermuteFunctionParameters(22, 105, {2, 2, 1})
                    .IsApplicable(context.get(), transformation_context),
                "Permutation has duplicates");
 #endif
 
-  // Type id is not an OpTypeFunction instruction
+  // Result id for new function type is not fresh.
   ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 42, {2, 1, 0})
                    .IsApplicable(context.get(), transformation_context));
 
-  // Type id has incorrect number of operands
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 9, {2, 1, 0})
-                   .IsApplicable(context.get(), transformation_context));
-
-  // OpTypeFunction has operands out of order
-  ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 18, {2, 1, 0})
-                   .IsApplicable(context.get(), transformation_context));
-
   // Successful transformations
   {
-    // Function has two operands of the same type:
-    // initial OpTypeFunction should be enough
-    TransformationPermuteFunctionParameters transformation(12, 9, {1, 0});
+    TransformationPermuteFunctionParameters transformation(12, 105, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     transformation.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
   }
   {
-    TransformationPermuteFunctionParameters transformation(28, 105, {1, 0});
+    TransformationPermuteFunctionParameters transformation(28, 106, {1, 0});
     ASSERT_TRUE(
         transformation.IsApplicable(context.get(), transformation_context));
     transformation.Apply(context.get(), &transformation_context);
@@ -313,8 +303,7 @@
          %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
+         %25 = OpTypeFunction %24 %7 %15
          %31 = OpConstant %6 255
          %33 = OpConstant %6 0
          %34 = OpConstant %6 1
@@ -422,7 +411,7 @@
          %55 = OpFunctionCall %8 %12 %54 %53
                OpReturnValue %55
                OpFunctionEnd
-         %28 = OpFunction %24 None %105
+         %28 = OpFunction %24 None %25
          %27 = OpFunctionParameter %7
          %26 = OpFunctionParameter %15
          %29 = OpLabel