spirv-fuzz: Support pointer types in FuzzerPassAddParameters (#3627)

For FuzzerPassAddParameters, adds pointer types (that have the storage
class Function or Private) to the pool of available types for new
parameters. If there are no variables of the chosen pointer type, it
invokes TransformationAddLocalVariable / TransformationAddGlobalVariable
to add one.

Part of #3403
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 262dbe3..3c8c65b 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -24,6 +24,8 @@
 #include "source/fuzz/transformation_add_constant_null.h"
 #include "source/fuzz/transformation_add_constant_scalar.h"
 #include "source/fuzz/transformation_add_global_undef.h"
+#include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_loop_preheader.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
@@ -595,5 +597,101 @@
   return &*function->FindBlock(preheader_id);
 }
 
+uint32_t FuzzerPass::FindOrCreateLocalVariable(
+    uint32_t pointer_type_id, uint32_t function_id,
+    bool pointee_value_is_irrelevant) {
+  auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
+  // No unused variables in release mode.
+  (void)pointer_type;
+  assert(pointer_type && pointer_type->AsPointer() &&
+         pointer_type->AsPointer()->storage_class() ==
+             SpvStorageClassFunction &&
+         "The pointer_type_id must refer to a defined pointer type with "
+         "storage class Function");
+  auto function = fuzzerutil::FindFunction(GetIRContext(), function_id);
+  assert(function && "The function must be defined.");
+
+  // First we try to find a suitable existing variable.
+  // All of the local variable declarations are located in the first block.
+  for (auto& instruction : *function->begin()) {
+    if (instruction.opcode() != SpvOpVariable) {
+      continue;
+    }
+    // The existing OpVariable must have type |pointer_type_id|.
+    if (instruction.type_id() != pointer_type_id) {
+      continue;
+    }
+    // Check if the found variable is marked with PointeeValueIsIrrelevant
+    // according to |pointee_value_is_irrelevant|.
+    if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
+            instruction.result_id()) != pointee_value_is_irrelevant) {
+      continue;
+    }
+    return instruction.result_id();
+  }
+
+  // No such variable was found. Apply a transformation to get one.
+  uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      GetIRContext(), pointer_type_id);
+  uint32_t result_id = GetFuzzerContext()->GetFreshId();
+  ApplyTransformation(TransformationAddLocalVariable(
+      result_id, pointer_type_id, function_id,
+      FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
+      pointee_value_is_irrelevant));
+  return result_id;
+}
+
+uint32_t FuzzerPass::FindOrCreateGlobalVariable(
+    uint32_t pointer_type_id, bool pointee_value_is_irrelevant) {
+  auto pointer_type = GetIRContext()->get_type_mgr()->GetType(pointer_type_id);
+  // No unused variables in release mode.
+  (void)pointer_type;
+  assert(
+      pointer_type && pointer_type->AsPointer() &&
+      (pointer_type->AsPointer()->storage_class() == SpvStorageClassPrivate ||
+       pointer_type->AsPointer()->storage_class() ==
+           SpvStorageClassWorkgroup) &&
+      "The pointer_type_id must refer to a defined pointer type with storage "
+      "class Private or Workgroup");
+
+  // First we try to find a suitable existing variable.
+  for (auto& instruction : GetIRContext()->module()->types_values()) {
+    if (instruction.opcode() != SpvOpVariable) {
+      continue;
+    }
+    // The existing OpVariable must have type |pointer_type_id|.
+    if (instruction.type_id() != pointer_type_id) {
+      continue;
+    }
+    // Check if the found variable is marked with PointeeValueIsIrrelevant
+    // according to |pointee_value_is_irrelevant|.
+    if (GetTransformationContext()->GetFactManager()->PointeeValueIsIrrelevant(
+            instruction.result_id()) != pointee_value_is_irrelevant) {
+      continue;
+    }
+    return instruction.result_id();
+  }
+
+  // No such variable was found. Apply a transformation to get one.
+  uint32_t pointee_type_id = fuzzerutil::GetPointeeTypeIdFromPointerType(
+      GetIRContext(), pointer_type_id);
+  auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
+      GetIRContext(), pointer_type_id);
+  uint32_t result_id = GetFuzzerContext()->GetFreshId();
+
+  // A variable with storage class Workgroup shouldn't have an initializer.
+  if (storage_class == SpvStorageClassWorkgroup) {
+    ApplyTransformation(TransformationAddGlobalVariable(
+        result_id, pointer_type_id, SpvStorageClassWorkgroup, 0,
+        pointee_value_is_irrelevant));
+  } else {
+    ApplyTransformation(TransformationAddGlobalVariable(
+        result_id, pointer_type_id, SpvStorageClassPrivate,
+        FindOrCreateZeroConstant(pointee_type_id, pointee_value_is_irrelevant),
+        pointee_value_is_irrelevant));
+  }
+  return result_id;
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index c8d4a44..423b443 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -293,6 +293,27 @@
   // reachable in the CFG (and thus has at least 2 predecessors).
   opt::BasicBlock* GetOrCreateSimpleLoopPreheader(uint32_t header_id);
 
+  // Returns the id of an available local variable (storage class Function) with
+  // the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. If there is no such variable, it creates one
+  // in the |function| adding a zero initializer constant that is irrelevant.
+  // The new variable has the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. The function returns the id of the created
+  // variable.
+  uint32_t FindOrCreateLocalVariable(uint32_t pointer_type_id,
+                                     uint32_t function_id,
+                                     bool pointee_value_is_irrelevant);
+
+  // Returns the id of an available global variable (storage class Private or
+  // Workgroup) with the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. If there is no such variable, it creates
+  // one, adding a zero initializer constant that is irrelevant. The new
+  // variable has the fact PointeeValueIsIrrelevant set according to
+  // |pointee_value_is_irrelevant|. The function returns the id of the created
+  // variable.
+  uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id,
+                                      bool pointee_value_is_irrelevant);
+
  private:
   opt::IRContext* ir_context_;
   TransformationContext* transformation_context_;
diff --git a/source/fuzz/fuzzer_pass_add_parameters.cpp b/source/fuzz/fuzzer_pass_add_parameters.cpp
index c5c9c33..3600a0f 100644
--- a/source/fuzz/fuzzer_pass_add_parameters.cpp
+++ b/source/fuzz/fuzzer_pass_add_parameters.cpp
@@ -69,14 +69,66 @@
     auto num_new_parameters =
         GetFuzzerContext()->GetRandomNumberOfNewParameters(
             GetNumberOfParameters(function));
+
     for (uint32_t i = 0; i < num_new_parameters; ++i) {
+      auto current_type_id =
+          type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)];
+      auto current_type =
+          GetIRContext()->get_type_mgr()->GetType(current_type_id);
+      std::map<uint32_t, uint32_t> call_parameter_ids;
+
+      // Consider the case when a pointer type was selected.
+      if (current_type->kind() == opt::analysis::Type::kPointer) {
+        auto storage_class = fuzzerutil::GetStorageClassFromPointerType(
+            GetIRContext(), current_type_id);
+        switch (storage_class) {
+          case SpvStorageClassFunction: {
+            // In every caller find or create a local variable that has the
+            // selected type.
+            for (auto* instr :
+                 fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
+              auto block = GetIRContext()->get_instr_block(instr);
+              auto function_id = block->GetParent()->result_id();
+              uint32_t variable_id =
+                  FindOrCreateLocalVariable(current_type_id, function_id, true);
+              call_parameter_ids[instr->result_id()] = variable_id;
+            }
+          } break;
+          case SpvStorageClassPrivate:
+          case SpvStorageClassWorkgroup: {
+            // If there exists at least one caller, find or create a global
+            // variable that has the selected type.
+            std::vector<opt::Instruction*> callers =
+                fuzzerutil::GetCallers(GetIRContext(), function.result_id());
+            if (!callers.empty()) {
+              uint32_t variable_id =
+                  FindOrCreateGlobalVariable(current_type_id, true);
+              for (auto* instr : callers) {
+                call_parameter_ids[instr->result_id()] = variable_id;
+              }
+            }
+          } break;
+          default:
+            break;
+        }
+      } else {
+        // If there exists at least one caller, find or create a zero constant
+        // that has the selected type.
+        std::vector<opt::Instruction*> callers =
+            fuzzerutil::GetCallers(GetIRContext(), function.result_id());
+        if (!callers.empty()) {
+          uint32_t constant_id =
+              FindOrCreateZeroConstant(current_type_id, true);
+          for (auto* instr :
+               fuzzerutil::GetCallers(GetIRContext(), function.result_id())) {
+            call_parameter_ids[instr->result_id()] = constant_id;
+          }
+        }
+      }
+
       ApplyTransformation(TransformationAddParameter(
           function.result_id(), GetFuzzerContext()->GetFreshId(),
-          // We mark the constant as irrelevant so that we can replace it with a
-          // more interesting value later.
-          FindOrCreateZeroConstant(
-              type_candidates[GetFuzzerContext()->RandomIndex(type_candidates)],
-              true),
+          current_type_id, std::move(call_parameter_ids),
           GetFuzzerContext()->GetFreshId()));
     }
   }
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 15a9b02..2aa113f 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -754,15 +754,17 @@
   // Fresh id for a new parameter.
   uint32 parameter_fresh_id = 2;
 
-  // 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;
+  // Type id for a new parameter.
+  uint32 parameter_type_id = 3;
+
+  // A map that maps from the OpFunctionCall id to the id that will be passed as the new
+  // parameter at that call site. It must have the same type as that of the new parameter.
+  repeated UInt32Pair call_parameter_ids = 4;
 
   // 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;
+  uint32 function_type_fresh_id = 5;
 
 }
 
diff --git a/source/fuzz/transformation_add_parameter.cpp b/source/fuzz/transformation_add_parameter.cpp
index 6c0ab28..99bbbdb 100644
--- a/source/fuzz/transformation_add_parameter.cpp
+++ b/source/fuzz/transformation_add_parameter.cpp
@@ -24,17 +24,20 @@
     : message_(message) {}
 
 TransformationAddParameter::TransformationAddParameter(
-    uint32_t function_id, uint32_t parameter_fresh_id, uint32_t initializer_id,
+    uint32_t function_id, uint32_t parameter_fresh_id,
+    uint32_t parameter_type_id, std::map<uint32_t, uint32_t> call_parameter_ids,
     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_parameter_type_id(parameter_type_id);
+  *message_.mutable_call_parameter_ids() =
+      fuzzerutil::MapToRepeatedUInt32Pair(call_parameter_ids);
   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
+  // Check that function exists.
   const auto* function =
       fuzzerutil::FindFunction(ir_context, message_.function_id());
   if (!function ||
@@ -42,22 +45,59 @@
     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) {
+  // The type must be supported.
+  uint32_t new_parameter_type_id = message_.parameter_type_id();
+  auto new_parameter_type =
+      ir_context->get_type_mgr()->GetType(new_parameter_type_id);
+  if (!new_parameter_type) {
+    return false;
+  }
+  if (!IsParameterTypeSupported(*new_parameter_type)) {
     return false;
   }
 
-  // Check that initializer's type is valid.
-  const auto* initializer_type =
-      ir_context->get_type_mgr()->GetType(initializer_inst->type_id());
+  // Iterate over all callers.
+  std::map<uint32_t, uint32_t> call_parameter_ids_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
+  for (auto* instr :
+       fuzzerutil::GetCallers(ir_context, message_.function_id())) {
+    uint32_t caller_id = instr->result_id();
 
-  if (!initializer_type || !IsParameterTypeSupported(*initializer_type)) {
-    return false;
+    // If there is no entry for this caller, return false.
+    if (call_parameter_ids_map.find(caller_id) ==
+        call_parameter_ids_map.end()) {
+      return false;
+    }
+    uint32_t value_id = call_parameter_ids_map[caller_id];
+
+    auto value_instr = ir_context->get_def_use_mgr()->GetDef(value_id);
+    if (!value_instr) {
+      return false;
+    }
+    // If the id of the value of the map is not available before the caller,
+    // return false.
+    // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3722):
+    //      This can potentially trigger a bug if the caller is in an
+    //      unreachable block. fuzzerutil::IdIsAvailableBeforeInstruction uses
+    //      dominator analysis to check that value_id is available and the
+    //      domination rules are not defined for unreachable blocks.
+    //      The following code should be refactored.
+    if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr,
+                                                    value_id)) {
+      return false;
+    }
+
+    // The type of the value must be defined.
+    uint32_t value_type_id = fuzzerutil::GetTypeId(ir_context, value_id);
+    if (!value_type_id) {
+      return false;
+    }
+
+    // Type of every value of the map must be the same for all callers.
+    if (new_parameter_type_id != value_type_id) {
+      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();
@@ -66,13 +106,17 @@
 void TransformationAddParameter::Apply(
     opt::IRContext* ir_context,
     TransformationContext* transformation_context) const {
-  // Find the function that will be transformed
+  // Find the function that will be transformed.
   auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
   assert(function && "Can't find the function");
 
-  const auto new_parameter_type_id =
-      fuzzerutil::GetTypeId(ir_context, message_.initializer_id());
-  assert(new_parameter_type_id != 0 && "Initializer has invalid type");
+  std::map<uint32_t, uint32_t> call_parameter_ids_map =
+      fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
+
+  uint32_t new_parameter_type_id = message_.parameter_type_id();
+  auto new_parameter_type =
+      ir_context->get_type_mgr()->GetType(new_parameter_type_id);
+  assert(new_parameter_type && "New parameter has invalid type.");
 
   // Add new parameters to the function.
   function->AddParameter(MakeUnique<opt::Instruction>(
@@ -81,17 +125,24 @@
 
   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.
-
-  // Mark new parameter as irrelevant so that we can replace its use with some
-  // other id.
-  transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
-      message_.parameter_fresh_id());
+  // If the |new_parameter_type_id| is not a pointer type, mark id as
+  // irrelevant so that we can replace its use with some other id. If the
+  // |new_parameter_type_id| is a pointer type, we cannot mark it with
+  // IdIsIrrelevant, because this pointer might be replaced by a pointer from
+  // original shader. This would change the semantics of the module. In the case
+  // of a pointer type we mark it with PointeeValueIsIrrelevant.
+  if (new_parameter_type->kind() != opt::analysis::Type::kPointer) {
+    transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
+        message_.parameter_fresh_id());
+  } else {
+    transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
+        message_.parameter_fresh_id());
+  }
 
   // Fix all OpFunctionCall instructions.
   for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
-    inst->AddOperand({SPV_OPERAND_TYPE_ID, {message_.initializer_id()}});
+    inst->AddOperand(
+        {SPV_OPERAND_TYPE_ID, {call_parameter_ids_map[inst->result_id()]}});
   }
 
   // Update function's type.
@@ -145,6 +196,19 @@
                          [](const opt::analysis::Type* element_type) {
                            return IsParameterTypeSupported(*element_type);
                          });
+    case opt::analysis::Type::kPointer: {
+      auto storage_class = type.AsPointer()->storage_class();
+      switch (storage_class) {
+        case SpvStorageClassPrivate:
+        case SpvStorageClassFunction:
+        case SpvStorageClassWorkgroup: {
+          auto pointee_type = type.AsPointer()->pointee_type();
+          return IsParameterTypeSupported(*pointee_type);
+        }
+        default:
+          return false;
+      }
+    }
     default:
       return false;
   }
diff --git a/source/fuzz/transformation_add_parameter.h b/source/fuzz/transformation_add_parameter.h
index e6b9019..361c01a 100644
--- a/source/fuzz/transformation_add_parameter.h
+++ b/source/fuzz/transformation_add_parameter.h
@@ -29,14 +29,19 @@
       const protobufs::TransformationAddParameter& message);
 
   TransformationAddParameter(uint32_t function_id, uint32_t parameter_fresh_id,
-                             uint32_t initializer_id,
+                             uint32_t parameter_type_id,
+                             std::map<uint32_t, uint32_t> call_parameter_ids,
                              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 must be supported by this transformation
-  //   as specified by IsParameterTypeSupported function.
+  // - |parameter_type_id| is a type id of the new parameter. The type must be
+  //   supported by this transformation as specified by IsParameterTypeSupported
+  //   function.
+  // - |call_parameter_id| must map from every id of an OpFunctionCall
+  //   instruction of this function to the id that will be passed as the new
+  //   parameter at that call site. There could be no callers, therefore this
+  //   map can be empty.
   // - |parameter_fresh_id| and |function_type_fresh_id| are fresh ids and are
   //   not equal.
   bool IsApplicable(
@@ -46,8 +51,8 @@
   // - 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.
+  // - Adds an argument to every caller of the function to account for the added
+  //   parameter. The argument is the value in |call_parameter_id| map.
   void Apply(opt::IRContext* ir_context,
              TransformationContext* transformation_context) const override;
 
diff --git a/test/fuzz/transformation_add_parameter_test.cpp b/test/fuzz/transformation_add_parameter_test.cpp
index c57c738..4826b95 100644
--- a/test/fuzz/transformation_add_parameter_test.cpp
+++ b/test/fuzz/transformation_add_parameter_test.cpp
@@ -13,14 +13,13 @@
 // 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) {
+TEST(TransformationAddParameterTest, NonPointerBasicTest) {
   std::string shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
@@ -108,53 +107,53 @@
                                                validator_options);
 
   // Can't modify entry point function.
-  ASSERT_FALSE(TransformationAddParameter(4, 60, 12, 61)
+  ASSERT_FALSE(TransformationAddParameter(4, 60, 7, {{}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
-  // There is no function with result id 29.
-  ASSERT_FALSE(TransformationAddParameter(60, 60, 8, 61)
+  // There is no function with result id 60.
+  ASSERT_FALSE(TransformationAddParameter(60, 60, 11, {{}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
   // Parameter id is not fresh.
-  ASSERT_FALSE(TransformationAddParameter(9, 14, 8, 61)
+  ASSERT_FALSE(TransformationAddParameter(9, 14, 11, {{{13, 8}}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
   // Function type id is not fresh.
-  ASSERT_FALSE(TransformationAddParameter(9, 60, 8, 14)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 14)
                    .IsApplicable(context.get(), transformation_context));
 
   // Function type id and parameter type id are equal.
-  ASSERT_FALSE(TransformationAddParameter(9, 60, 8, 60)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 8}}}, 60)
                    .IsApplicable(context.get(), transformation_context));
 
   // Parameter's initializer doesn't exist.
-  ASSERT_FALSE(TransformationAddParameter(9, 60, 60, 61)
+  ASSERT_FALSE(TransformationAddParameter(9, 60, 11, {{{13, 60}}}, 61)
                    .IsApplicable(context.get(), transformation_context));
 
   // Correct transformations.
   {
-    TransformationAddParameter correct(9, 60, 8, 61);
+    TransformationAddParameter correct(9, 60, 11, {{{13, 8}}}, 61);
     ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
     correct.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
     ASSERT_TRUE(fact_manager.IdIsIrrelevant(60));
   }
   {
-    TransformationAddParameter correct(17, 62, 12, 63);
+    TransformationAddParameter correct(17, 62, 7, {{}}, 63);
     ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
     correct.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
     ASSERT_TRUE(fact_manager.IdIsIrrelevant(62));
   }
   {
-    TransformationAddParameter correct(29, 64, 33, 65);
+    TransformationAddParameter correct(29, 64, 31, {{}}, 65);
     ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
     correct.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
     ASSERT_TRUE(fact_manager.IdIsIrrelevant(64));
   }
   {
-    TransformationAddParameter correct(34, 66, 12, 67);
+    TransformationAddParameter correct(34, 66, 7, {{}}, 67);
     ASSERT_TRUE(correct.IsApplicable(context.get(), transformation_context));
     correct.Apply(context.get(), &transformation_context);
     ASSERT_TRUE(IsValid(env, context.get()));
@@ -240,10 +239,842 @@
                OpReturn
                OpFunctionEnd
   )";
-
   ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
 }
 
+TEST(TransformationAddParameterTest, NonPointerNotApplicableTest) {
+  // This types handles case of adding a new parameter of a non-pointer type
+  // where the transformation is not applicable.
+  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"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %24 "f1"
+               OpName %27 "f2"
+               OpName %30 "i1"
+               OpName %31 "i2"
+               OpName %32 "param"
+               OpName %35 "i3"
+               OpName %36 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %18 = OpConstant %8 2
+         %22 = OpTypeFloat 32
+         %23 = OpTypePointer Private %22
+         %24 = OpVariable %23 Private
+         %25 = OpConstant %22 1
+         %26 = OpTypePointer Function %22
+         %28 = OpConstant %22 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %27 = OpVariable %26 Function
+         %30 = OpVariable %9 Function
+         %31 = OpVariable %9 Function
+         %32 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+               OpStore %24 %25
+               OpStore %27 %28
+         %29 = OpFunctionCall %2 %6
+               OpStore %30 %18
+         %33 = OpLoad %8 %30
+               OpStore %32 %33
+         %34 = OpFunctionCall %8 %12 %32
+               OpStore %31 %34
+         %37 = OpLoad %8 %31
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         ; %39 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpLoad %8 %11
+         %19 = OpIAdd %8 %17 %18
+               OpReturnValue %19
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: Id 19 is not available in the caller that has id 34.
+  TransformationAddParameter transformation_bad_1(12, 50, 8,
+                                                  {{{34, 19}, {38, 19}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Id 8 does not have a type.
+  TransformationAddParameter transformation_bad_2(12, 50, 8,
+                                                  {{{34, 8}, {38, 8}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: Types of id 25 and id 18 are different.
+  TransformationAddParameter transformation_bad_3(12, 50, 22,
+                                                  {{{34, 25}, {38, 18}}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Function with id 14 does not have any callers.
+  // Bad: Id 18 is not a vaild type.
+  TransformationAddParameter transformation_bad_4(14, 50, 18, {{}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+
+  // Function with id 14 does not have any callers.
+  // Bad:  Id 3 refers to OpTypeVoid, which is not supported.
+  TransformationAddParameter transformation_bad_6(14, 50, 3, {{}}, 51);
+  ASSERT_FALSE(
+      transformation_bad_6.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddParameterTest, PointerFunctionTest) {
+  // This types handles case of adding a new parameter of a pointer type with
+  // storage class Function.
+  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"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Output %26
+         %61 = OpVariable %60 Output
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: Pointer of id 61 has storage class Output, which is not supported.
+  TransformationAddParameter transformation_bad_1(12, 50, 60,
+                                                  {{{38, 61}, {42, 61}}}, 51);
+
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Good: Local variable of id 31 is defined in the caller (main).
+  TransformationAddParameter transformation_good_1(12, 50, 30,
+                                                   {{{38, 31}, {42, 31}}}, 51);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Local variable of id 34 is defined in the caller (main).
+  TransformationAddParameter transformation_good_2(14, 52, 9, {{{43, 34}}}, 53);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Local variable of id 39 is defined in the caller (main).
+  TransformationAddParameter transformation_good_3(6, 54, 9, {{{33, 39}}}, 55);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: This adds another pointer parameter to the function of id 6.
+  TransformationAddParameter transformation_good_4(6, 56, 30, {{{33, 31}}}, 57);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_4.Apply(context.get(), &transformation_context);
+  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"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Output %26
+         %61 = OpVariable %60 Output
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+         %10 = OpTypeFunction %8 %9 %30
+         %53 = OpTypeFunction %2 %9
+         %57 = OpTypeFunction %2 %9 %30
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6 %39 %31
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36 %31
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40 %31
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14 %34
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %57
+         %54 = OpFunctionParameter %9
+         %56 = OpFunctionParameter %30
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %50 = OpFunctionParameter %30
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %53
+         %52 = OpFunctionParameter %9
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointerPrivateWorkgroupTest) {
+  // This types handles case of adding a new parameter of a pointer type with
+  // storage class Private or Workgroup.
+  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"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %10 = OpTypeFunction %8 %9
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Workgroup %26
+         %61 = OpVariable %60 Workgroup
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %3
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %3
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+    )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_1(12, 70, 27,
+                                                   {{{38, 28}, {42, 28}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_2(12, 72, 27,
+                                                   {{{38, 28}, {42, 28}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_3(6, 74, 27, {{{33, 28}}}, 75);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_4(6, 76, 60, {{{33, 61}}}, 77);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_4.Apply(context.get(), &transformation_context);
+
+  // Good: Global variable of id 28 (storage class Private) is defined in the
+  // caller (main).
+  TransformationAddParameter transformation_good_5(14, 78, 27, {{{43, 28}}},
+                                                   79);
+  ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_5.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Global variable of id 61 is (storage class Workgroup) is defined in
+  // the caller (main).
+  TransformationAddParameter transformation_good_6(14, 80, 60, {{{43, 61}}},
+                                                   81);
+  ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_6.Apply(context.get(), &transformation_context);
+  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"
+               OpName %6 "fun1("
+               OpName %12 "fun2(i1;"
+               OpName %11 "a"
+               OpName %14 "fun3("
+               OpName %17 "s"
+               OpName %24 "s"
+               OpName %28 "f1"
+               OpName %31 "f2"
+               OpName %34 "i1"
+               OpName %35 "i2"
+               OpName %36 "param"
+               OpName %39 "i3"
+               OpName %40 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %8 = OpTypeInt 32 1
+          %9 = OpTypePointer Function %8
+         %20 = OpConstant %8 2
+         %25 = OpConstant %8 0
+         %26 = OpTypeFloat 32
+         %27 = OpTypePointer Private %26
+         %28 = OpVariable %27 Private
+         %60 = OpTypePointer Workgroup %26
+         %61 = OpVariable %60 Workgroup
+         %29 = OpConstant %26 1
+         %30 = OpTypePointer Function %26
+         %32 = OpConstant %26 2
+         %10 = OpTypeFunction %8 %9 %27 %27
+         %75 = OpTypeFunction %2 %27 %60
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %31 = OpVariable %30 Function
+         %34 = OpVariable %9 Function
+         %35 = OpVariable %9 Function
+         %36 = OpVariable %9 Function
+         %39 = OpVariable %9 Function
+         %40 = OpVariable %9 Function
+               OpStore %28 %29
+               OpStore %31 %32
+         %33 = OpFunctionCall %2 %6 %28 %61
+               OpStore %34 %20
+         %37 = OpLoad %8 %34
+               OpStore %36 %37
+         %38 = OpFunctionCall %8 %12 %36 %28 %28
+               OpStore %35 %38
+         %41 = OpLoad %8 %35
+               OpStore %40 %41
+         %42 = OpFunctionCall %8 %12 %40 %28 %28
+               OpStore %39 %42
+         %43 = OpFunctionCall %2 %14 %28 %61
+               OpReturn
+               OpFunctionEnd
+          %6 = OpFunction %2 None %75
+         %74 = OpFunctionParameter %27
+         %76 = OpFunctionParameter %60
+          %7 = OpLabel
+               OpReturn
+               OpFunctionEnd
+         %12 = OpFunction %8 None %10
+         %11 = OpFunctionParameter %9
+         %70 = OpFunctionParameter %27
+         %72 = OpFunctionParameter %27
+         %13 = OpLabel
+         %17 = OpVariable %9 Function
+         %18 = OpLoad %8 %11
+               OpStore %17 %18
+         %19 = OpLoad %8 %17
+         %21 = OpIAdd %8 %19 %20
+               OpReturnValue %21
+               OpFunctionEnd
+         %14 = OpFunction %2 None %75
+         %78 = OpFunctionParameter %27
+         %80 = OpFunctionParameter %60
+         %15 = OpLabel
+         %24 = OpVariable %9 Function
+               OpStore %24 %25
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointerMoreEntriesInMapTest) {
+  // This types handles case where call_parameter_id has an entry for at least
+  // every caller (there are more entries than it is necessary).
+  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"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %19 "i1"
+               OpName %21 "i2"
+               OpName %22 "i3"
+               OpName %24 "i4"
+               OpName %25 "param"
+               OpName %28 "i5"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %8 = OpTypeFunction %6 %7
+         %15 = OpConstant %6 2
+         %20 = OpConstant %6 1
+         %23 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %19 %20
+               OpStore %21 %15
+               OpStore %22 %23
+         %26 = OpLoad %6 %19
+               OpStore %25 %26
+         %27 = OpFunctionCall %6 %10 %25
+               OpStore %24 %27
+         %30 = OpLoad %6 %21
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29
+               OpStore %28 %31
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               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);
+
+  // Good: Local variable of id 21 is defined in every caller (id 27 and id 31).
+  TransformationAddParameter transformation_good_1(
+      10, 70, 7, {{{27, 21}, {31, 21}, {30, 21}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Good: Local variable of id 28 is defined in every caller (id 27 and id 31).
+  TransformationAddParameter transformation_good_2(
+      10, 72, 7, {{{27, 28}, {31, 28}, {14, 21}, {16, 14}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+  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"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %19 "i1"
+               OpName %21 "i2"
+               OpName %22 "i3"
+               OpName %24 "i4"
+               OpName %25 "param"
+               OpName %28 "i5"
+               OpName %29 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %15 = OpConstant %6 2
+         %20 = OpConstant %6 1
+         %23 = OpConstant %6 3
+          %8 = OpTypeFunction %6 %7 %7 %7
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %19 = OpVariable %7 Function
+         %21 = OpVariable %7 Function
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %28 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+               OpStore %19 %20
+               OpStore %21 %15
+               OpStore %22 %23
+         %26 = OpLoad %6 %19
+               OpStore %25 %26
+         %27 = OpFunctionCall %6 %10 %25 %21 %28
+               OpStore %24 %27
+         %30 = OpLoad %6 %21
+               OpStore %29 %30
+         %31 = OpFunctionCall %6 %10 %29 %21 %28
+               OpStore %28 %31
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %70 = OpFunctionParameter %7
+         %72 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               OpFunctionEnd
+    )";
+  ASSERT_TRUE(IsEqual(env, expected_shader, context.get()));
+}
+
+TEST(TransformationAddParameterTest, PointeeValueIsIrrelevantTest) {
+  // This test checks if the transformation has correctly applied the
+  // PointeeValueIsIrrelevant fact for new pointer parameters.
+  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"
+               OpName %10 "fun(i1;"
+               OpName %9 "a"
+               OpName %12 "s"
+               OpName %20 "b"
+               OpName %22 "i1"
+               OpName %24 "i2"
+               OpName %25 "i3"
+               OpName %26 "param"
+               OpName %29 "i4"
+               OpName %30 "param"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+         %50 = OpTypePointer Workgroup %6
+         %51 = OpVariable %50 Workgroup
+          %8 = OpTypeFunction %6 %7
+         %15 = OpConstant %6 2
+         %19 = OpTypePointer Private %6
+         %20 = OpVariable %19 Private
+         %21 = OpConstant %6 0
+         %23 = OpConstant %6 1
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+         %22 = OpVariable %7 Function
+         %24 = OpVariable %7 Function
+         %25 = OpVariable %7 Function
+         %26 = OpVariable %7 Function
+         %29 = OpVariable %7 Function
+         %30 = OpVariable %7 Function
+               OpStore %20 %21
+               OpStore %22 %23
+               OpStore %24 %15
+         %27 = OpLoad %6 %22
+               OpStore %26 %27
+         %28 = OpFunctionCall %6 %10 %26
+               OpStore %25 %28
+         %31 = OpLoad %6 %24
+               OpStore %30 %31
+         %32 = OpFunctionCall %6 %10 %30
+               OpStore %29 %32
+               OpReturn
+               OpFunctionEnd
+         %10 = OpFunction %6 None %8
+          %9 = OpFunctionParameter %7
+         %11 = OpLabel
+         %12 = OpVariable %7 Function
+         %13 = OpLoad %6 %9
+               OpStore %12 %13
+         %14 = OpLoad %6 %12
+         %16 = OpIAdd %6 %14 %15
+               OpReturnValue %16
+               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);
+
+  TransformationAddParameter transformation_good_1(10, 70, 7,
+                                                   {{{28, 22}, {32, 22}}}, 71);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Function).
+  ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(70));
+
+  TransformationAddParameter transformation_good_2(10, 72, 19,
+                                                   {{{28, 20}, {32, 20}}}, 73);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Private).
+  ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(72));
+
+  TransformationAddParameter transformation_good_3(10, 74, 50,
+                                                   {{{28, 51}, {32, 51}}}, 75);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Check if the fact PointeeValueIsIrrelevant is set for the new parameter
+  // (storage class Workgroup).
+  ASSERT_TRUE(fact_manager.PointeeValueIsIrrelevant(74));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools