spirv-fuzz: Implement FuzzerPassAddParameters (#3399)

Fixes #3384.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index fd6c5c9..8893d3e 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -48,6 +48,7 @@
         fuzzer_pass_add_loads.h
         fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_no_contraction_decorations.h
+        fuzzer_pass_add_parameters.h
         fuzzer_pass_add_stores.h
         fuzzer_pass_add_vector_shuffle_instructions.h
         fuzzer_pass_adjust_branch_weights.h
@@ -94,6 +95,7 @@
         transformation_add_global_variable.h
         transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
+        transformation_add_parameters.h
         transformation_add_spec_constant_op.h
         transformation_add_type_array.h
         transformation_add_type_boolean.h
@@ -154,6 +156,7 @@
         fuzzer_pass_add_loads.cpp
         fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
+        fuzzer_pass_add_parameters.cpp
         fuzzer_pass_add_stores.cpp
         fuzzer_pass_add_vector_shuffle_instructions.cpp
         fuzzer_pass_adjust_branch_weights.cpp
@@ -199,6 +202,7 @@
         transformation_add_global_variable.cpp
         transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
+        transformation_add_parameters.cpp
         transformation_add_spec_constant_op.cpp
         transformation_add_type_array.cpp
         transformation_add_type_boolean.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 26585c7..36e7f90 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -32,6 +32,7 @@
 #include "source/fuzz/fuzzer_pass_add_loads.h"
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
+#include "source/fuzz/fuzzer_pass_add_parameters.h"
 #include "source/fuzz/fuzzer_pass_add_stores.h"
 #include "source/fuzz/fuzzer_pass_add_vector_shuffle_instructions.h"
 #include "source/fuzz/fuzzer_pass_adjust_branch_weights.h"
@@ -224,6 +225,9 @@
     MaybeAddPass<FuzzerPassAddLocalVariables>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddParameters>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddStores>(&passes, ir_context.get(),
                                       &transformation_context, &fuzzer_context,
                                       transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 7f10642..97e145b 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -38,6 +38,7 @@
 const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingNoContractionDecoration = {
     5, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingParameters = {5, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingStore = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorType = {20, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingVectorShuffle = {20, 70};
@@ -81,6 +82,10 @@
 const uint32_t kDefaultMaxLoopControlPeelCount = 100;
 const uint32_t kDefaultMaxLoopLimit = 20;
 const uint32_t kDefaultMaxNewArraySizeLimit = 100;
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3424):
+//  think whether there is a better limit on the maximum number of parameters.
+const uint32_t kDefaultMaxNumberOfFunctionParameters = 128;
+const uint32_t kDefaultMaxNumberOfNewParameters = 15;
 
 // Default functions for controlling how deep to go during recursive
 // generation/transformation. Keep them in alphabetical order.
@@ -104,6 +109,8 @@
       max_loop_control_peel_count_(kDefaultMaxLoopControlPeelCount),
       max_loop_limit_(kDefaultMaxLoopLimit),
       max_new_array_size_limit_(kDefaultMaxNewArraySizeLimit),
+      max_number_of_function_parameters_(kDefaultMaxNumberOfFunctionParameters),
+      max_number_of_new_parameters_(kDefaultMaxNumberOfNewParameters),
       go_deeper_in_constant_obfuscation_(
           kDefaultGoDeeperInConstantObfuscation) {
   chance_of_adding_access_chain_ =
@@ -129,6 +136,8 @@
       ChooseBetweenMinAndMax(kChanceOfAddingMatrixType);
   chance_of_adding_no_contraction_decoration_ =
       ChooseBetweenMinAndMax(kChanceOfAddingNoContractionDecoration);
+  chance_of_adding_parameters =
+      ChooseBetweenMinAndMax(kChanceOfAddingParameters);
   chance_of_adding_store_ = ChooseBetweenMinAndMax(kChanceOfAddingStore);
   chance_of_adding_vector_shuffle_ =
       ChooseBetweenMinAndMax(kChanceOfAddingVectorShuffle);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index f4bf1a7..4598124 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -135,6 +135,7 @@
   uint32_t GetChanceOfAddingNoContractionDecoration() {
     return chance_of_adding_no_contraction_decoration_;
   }
+  uint32_t GetChanceOfAddingParameters() { return chance_of_adding_parameters; }
   uint32_t GetChanceOfAddingStore() { return chance_of_adding_store_; }
   uint32_t GetChanceOfAddingVectorShuffle() {
     return chance_of_adding_vector_shuffle_;
@@ -210,6 +211,9 @@
   uint32_t GetMaximumEquivalenceClassSizeForDataSynonymFactClosure() {
     return max_equivalence_class_size_for_data_synonym_fact_closure_;
   }
+  uint32_t GetMaximumNumberOfFunctionParameters() {
+    return max_number_of_function_parameters_;
+  }
   std::pair<uint32_t, uint32_t> GetRandomBranchWeights() {
     std::pair<uint32_t, uint32_t> branch_weights = {0, 0};
 
@@ -245,6 +249,12 @@
   uint32_t GetRandomLoopLimit() {
     return random_generator_->RandomUint32(max_loop_limit_);
   }
+  uint32_t GetRandomNumberOfNewParameters(uint32_t num_of_params) {
+    assert(num_of_params < GetMaximumNumberOfFunctionParameters());
+    return ChooseBetweenMinAndMax(
+        {1, std::min(max_number_of_new_parameters_,
+                     GetMaximumNumberOfFunctionParameters() - num_of_params)});
+  }
   uint32_t GetRandomSizeForNewArray() {
     // Ensure that the array size is non-zero.
     return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
@@ -273,6 +283,7 @@
   uint32_t chance_of_adding_local_variable_;
   uint32_t chance_of_adding_matrix_type_;
   uint32_t chance_of_adding_no_contraction_decoration_;
+  uint32_t chance_of_adding_parameters;
   uint32_t chance_of_adding_store_;
   uint32_t chance_of_adding_vector_shuffle_;
   uint32_t chance_of_adding_vector_type_;
@@ -309,6 +320,8 @@
   uint32_t max_loop_control_peel_count_;
   uint32_t max_loop_limit_;
   uint32_t max_new_array_size_limit_;
+  uint32_t max_number_of_function_parameters_;
+  uint32_t max_number_of_new_parameters_;
 
   // Functions to determine with what probability to go deeper when generating
   // or mutating constructs recursively.
diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp
new file mode 100644
index 0000000..c931302
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_parameters.cpp
@@ -0,0 +1,143 @@
+// 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/fuzzer_pass_add_parameters.h"
+
+#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"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddParameters::FuzzerPassAddParameters(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddParameters::~FuzzerPassAddParameters() = default;
+
+void FuzzerPassAddParameters::Apply() {
+  const auto& type_candidates = ComputeTypeCandidates();
+
+  if (type_candidates.empty()) {
+    // The module contains no suitable types to use in new parameters.
+    return;
+  }
+
+  // Iterate over all functions in the module.
+  for (const auto& function : *GetIRContext()->module()) {
+    // Skip all entry-point functions - we don't want to change those.
+    if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(),
+                                         function.result_id())) {
+      continue;
+    }
+
+    if (GetNumberOfParameters(function) >=
+        GetFuzzerContext()->GetMaximumNumberOfFunctionParameters()) {
+      continue;
+    }
+
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()->GetChanceOfAddingParameters())) {
+      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]);
+    }
+
+    // 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)));
+  }
+}
+
+std::vector<uint32_t> FuzzerPassAddParameters::ComputeTypeCandidates() const {
+  std::vector<uint32_t> result;
+
+  for (const auto* type_inst : GetIRContext()->module()->GetTypes()) {
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
+    //  the number of types we support here is limited by the number of types
+    //  supported by |FindOrCreateZeroConstant|.
+    switch (type_inst->opcode()) {
+      case SpvOpTypeBool:
+      case SpvOpTypeInt:
+      case SpvOpTypeFloat:
+      case SpvOpTypeArray:
+      case SpvOpTypeMatrix:
+      case SpvOpTypeVector:
+      case SpvOpTypeStruct: {
+        result.push_back(type_inst->result_id());
+      } break;
+      default:
+        // Ignore other types.
+        break;
+    }
+  }
+
+  return result;
+}
+
+uint32_t FuzzerPassAddParameters::GetNumberOfParameters(
+    const opt::Function& function) const {
+  const auto* type = GetIRContext()->get_type_mgr()->GetType(
+      function.DefInst().GetSingleWordInOperand(1));
+  assert(type && type->AsFunction());
+
+  return static_cast<uint32_t>(type->AsFunction()->param_types().size());
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_parameters.h b/source/fuzz/fuzzer_pass_add_parameters.h
new file mode 100644
index 0000000..feb009f
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_parameters.h
@@ -0,0 +1,51 @@
+// 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_ADD_PARAMETERS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_
+
+#include <vector>
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Randomly decides for each non-entry-point function in the module whether to
+// add new parameters to it. If so, randomly determines the number of parameters
+// to add, their type and creates constants used to initialize them.
+class FuzzerPassAddParameters : public FuzzerPass {
+ public:
+  FuzzerPassAddParameters(opt::IRContext* ir_context,
+                          TransformationContext* transformation_context,
+                          FuzzerContext* fuzzer_context,
+                          protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddParameters() override;
+
+  void Apply() override;
+
+ private:
+  // Uses types, defined in the module, to compute a vector of their ids, which
+  // will be used as type ids of new parameters.
+  std::vector<uint32_t> ComputeTypeCandidates() const;
+
+  // Returns number of parameters of |function|.
+  uint32_t GetNumberOfParameters(const opt::Function& function) const;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_PARAMETERS_H_
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h
index 3f32864..bc1031c 100644
--- a/source/fuzz/fuzzer_pass_permute_function_parameters.h
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h
@@ -34,7 +34,7 @@
       FuzzerContext* fuzzer_context,
       protobufs::TransformationSequence* transformations);
 
-  ~FuzzerPassPermuteFunctionParameters();
+  ~FuzzerPassPermuteFunctionParameters() override;
 
   void Apply() override;
 };
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 71273e7..b8d07cf 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -380,6 +380,7 @@
     TransformationReplaceLinearAlgebraInstruction replace_linear_algebra_instruction = 49;
     TransformationSwapConditionalBranchOperands swap_conditional_branch_operands = 50;
     TransformationPermutePhiOperands permute_phi_operands = 51;
+    TransformationAddParameters add_parameters = 52;
     // Add additional option using the next available number.
   }
 }
@@ -623,6 +624,27 @@
 
 }
 
+message TransformationAddParameters {
+
+  // Adds new parameters 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;
+
+  // Type ids of parameters to add to the function.
+  repeated uint32 new_parameter_type = 3;
+
+  // Result ids for new parameters.
+  repeated uint32 new_parameter_id = 4;
+
+  // Constants to initialize new parameters from.
+  repeated uint32 constant_id = 5;
+
+}
+
 message TransformationAddSpecConstantOp {
 
   // Adds OpSpecConstantOp into the module.
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 686b46f..3177de0 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +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_spec_constant_op.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
@@ -114,6 +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::kAddSpecConstantOp:
       return MakeUnique<TransformationAddSpecConstantOp>(
           message.add_spec_constant_op());
diff --git a/source/fuzz/transformation_add_parameters.cpp b/source/fuzz/transformation_add_parameters.cpp
new file mode 100644
index 0000000..28af854
--- /dev/null
+++ b/source/fuzz/transformation_add_parameters.cpp
@@ -0,0 +1,201 @@
+// 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
new file mode 100644
index 0000000..681195c
--- /dev/null
+++ b/source/fuzz/transformation_add_parameters.h
@@ -0,0 +1,70 @@
+// 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/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 9a59742..7d9c35c 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -37,6 +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_type_array_test.cpp
           transformation_add_type_boolean_test.cpp
           transformation_add_type_float_test.cpp
diff --git a/test/fuzz/transformation_add_parameters_test.cpp b/test/fuzz/transformation_add_parameters_test.cpp
new file mode 100644
index 0000000..37c2e93
--- /dev/null
+++ b/test/fuzz/transformation_add_parameters_test.cpp
@@ -0,0 +1,177 @@
+// 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