spirv-fuzz: TransformationReplaceAddSubMulWithCarryingExtended (#3598)

Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul with
OpUMulExtended or OpSMulExtended and stores the result into a fresh_id
representing a structure. Extracts the first element of the result into
the original result_id. This value is the same as the result of the
original instruction.

Fixes #3577
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 4564fcf..a056630 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -76,6 +76,7 @@
         fuzzer_pass_permute_instructions.h
         fuzzer_pass_permute_phi_operands.h
         fuzzer_pass_push_ids_through_variables.h
+        fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
         fuzzer_pass_replace_copy_memories_with_loads_stores.h
         fuzzer_pass_replace_copy_objects_with_stores_loads.h
         fuzzer_pass_replace_linear_algebra_instructions.h
@@ -143,6 +144,7 @@
         transformation_permute_phi_operands.h
         transformation_push_id_through_variable.h
         transformation_record_synonymous_constants.h
+        transformation_replace_add_sub_mul_with_carrying_extended.h
         transformation_replace_boolean_constant_with_constant_binary.h
         transformation_replace_constant_with_uniform.h
         transformation_replace_copy_memory_with_load_store.h
@@ -211,6 +213,7 @@
         fuzzer_pass_permute_instructions.cpp
         fuzzer_pass_permute_phi_operands.cpp
         fuzzer_pass_push_ids_through_variables.cpp
+        fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
         fuzzer_pass_replace_copy_memories_with_loads_stores.cpp
         fuzzer_pass_replace_copy_objects_with_stores_loads.cpp
         fuzzer_pass_replace_linear_algebra_instructions.cpp
@@ -277,6 +280,7 @@
         transformation_permute_phi_operands.cpp
         transformation_push_id_through_variable.cpp
         transformation_record_synonymous_constants.cpp
+        transformation_replace_add_sub_mul_with_carrying_extended.cpp
         transformation_replace_boolean_constant_with_constant_binary.cpp
         transformation_replace_constant_with_uniform.cpp
         transformation_replace_copy_memory_with_load_store.cpp
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index c1e456c..9ba5442 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -82,6 +82,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPermutingPhiOperands = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfPushingIdThroughVariable = {5, 50};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingAddSubMulWithCarryingExtended = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyMemoryWithLoadStore =
     {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingCopyObjectWithStoreLoad =
@@ -227,6 +229,8 @@
       ChooseBetweenMinAndMax(kChanceOfPermutingPhiOperands);
   chance_of_pushing_id_through_variable_ =
       ChooseBetweenMinAndMax(kChanceOfPushingIdThroughVariable);
+  chance_of_replacing_add_sub_mul_with_carrying_extended_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingAddSubMulWithCarryingExtended);
   chance_of_replacing_copy_memory_with_load_store_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingCopyMemoryWithLoadStore);
   chance_of_replacing_copyobject_with_store_load_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 10f3630..f4f9cf8 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -221,6 +221,9 @@
   uint32_t GetChanceOfPushingIdThroughVariable() {
     return chance_of_pushing_id_through_variable_;
   }
+  uint32_t GetChanceOfReplacingAddSubMulWithCarryingExtended() {
+    return chance_of_replacing_add_sub_mul_with_carrying_extended_;
+  }
   uint32_t GetChanceOfReplacingCopyMemoryWithLoadStore() {
     return chance_of_replacing_copy_memory_with_load_store_;
   }
@@ -375,6 +378,7 @@
   uint32_t chance_of_permuting_parameters_;
   uint32_t chance_of_permuting_phi_operands_;
   uint32_t chance_of_pushing_id_through_variable_;
+  uint32_t chance_of_replacing_add_sub_mul_with_carrying_extended_;
   uint32_t chance_of_replacing_copy_memory_with_load_store_;
   uint32_t chance_of_replacing_copyobject_with_store_load_;
   uint32_t chance_of_replacing_id_with_synonym_;
diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
new file mode 100644
index 0000000..d506de6
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.cpp
@@ -0,0 +1,76 @@
+// Copyright (c) 2020 Google LLC
+//
+// 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_replace_adds_subs_muls_with_carrying_extended.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
+}  // namespace
+
+FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
+    FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::
+    ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() = default;
+
+void FuzzerPassReplaceAddsSubsMulsWithCarryingExtended::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decide whether to apply the transformation.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()
+                    ->GetChanceOfReplacingAddSubMulWithCarryingExtended())) {
+          continue;
+        }
+
+        // Check if the transformation can be applied to this instruction.
+        if (!TransformationReplaceAddSubMulWithCarryingExtended::
+                IsInstructionSuitable(GetIRContext(), instruction)) {
+          continue;
+        }
+
+        // Get the operand type id. We know that both operands have the same
+        // type.
+        uint32_t operand_type_id =
+            GetIRContext()
+                ->get_def_use_mgr()
+                ->GetDef(instruction.GetSingleWordInOperand(
+                    kArithmeticInstructionIndexLeftInOperand))
+                ->type_id();
+
+        // Ensure the required struct type exists. The struct type is based on
+        // the operand type.
+        FindOrCreateStructType({operand_type_id, operand_type_id});
+
+        ApplyTransformation(TransformationReplaceAddSubMulWithCarryingExtended(
+            GetFuzzerContext()->GetFreshId(), instruction.result_id()));
+      }
+    }
+  }
+}
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
new file mode 100644
index 0000000..f224d2b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_adds_subs_muls_with_carrying_extended.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2020 Google LLC
+//
+// 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 SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H
+#define SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A fuzzer pass that replaces instructions OpIAdd, OpISub, OpIMul with pairs of
+// instructions. The first one (OpIAddCarry, OpISubBorrow, OpUMulExtended,
+// OpSMulExtended) computes the result into a struct. The second one extracts
+// the appropriate component from the struct to yield the original result.
+class FuzzerPassReplaceAddsSubsMulsWithCarryingExtended : public FuzzerPass {
+ public:
+  FuzzerPassReplaceAddsSubsMulsWithCarryingExtended(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceAddsSubsMulsWithCarryingExtended() override;
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_FUZZER_PASS_REPLACE_ADDS_SUBS_MULS_WITH_CARRYING_EXTENDED_H
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index e154f2a..45414f6 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -410,6 +410,7 @@
     TransformationAddLoopPreheader add_loop_preheader = 63;
     TransformationMoveInstructionDown move_instruction_down = 64;
     TransformationMakeVectorOperationDynamic make_vector_operation_dynamic = 65;
+    TransformationReplaceAddSubMulWithCarryingExtended replace_add_sub_mul_with_carrying_extended = 66;
     // Add additional option using the next available number.
   }
 }
@@ -1285,6 +1286,24 @@
 
 }
 
+message TransformationReplaceAddSubMulWithCarryingExtended {
+
+   // Replaces OpIAdd with OpIAddCarry, OpISub with OpISubBorrow, OpIMul
+   // with OpUMulExtended or OpSMulExtended (depending on the signedness
+   // of the operands) and stores the result into a |struct_fresh_id|.
+   // In the original instruction the result type id and the type ids of
+   // the operands must be the same. Then the transformation extracts
+   // the first element of the result into the original |result_id|.
+   // This value is the same as the result of the original instruction.
+
+   // The fresh id of the intermediate result.
+   uint32 struct_fresh_id = 1;
+
+   // The result id of the original instruction.
+   uint32 result_id = 2;
+
+}
+
 message TransformationReplaceParameterWithGlobal {
 
   // Removes parameter with result id |parameter_id| from its function
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 0824d4f..727309a 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -63,6 +63,7 @@
 #include "source/fuzz/transformation_permute_phi_operands.h"
 #include "source/fuzz/transformation_push_id_through_variable.h"
 #include "source/fuzz/transformation_record_synonymous_constants.h"
+#include "source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h"
 #include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
 #include "source/fuzz/transformation_replace_constant_with_uniform.h"
 #include "source/fuzz/transformation_replace_copy_memory_with_load_store.h"
@@ -221,9 +222,9 @@
       return MakeUnique<TransformationRecordSynonymousConstants>(
           message.record_synonymous_constants());
     case protobufs::Transformation::TransformationCase::
-        kReplaceParameterWithGlobal:
-      return MakeUnique<TransformationReplaceParameterWithGlobal>(
-          message.replace_parameter_with_global());
+        kReplaceAddSubMulWithCarryingExtended:
+      return MakeUnique<TransformationReplaceAddSubMulWithCarryingExtended>(
+          message.replace_add_sub_mul_with_carrying_extended());
     case protobufs::Transformation::TransformationCase::
         kReplaceBooleanConstantWithConstantBinary:
       return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
@@ -252,6 +253,10 @@
       return MakeUnique<TransformationReplaceLoadStoreWithCopyMemory>(
           message.replace_load_store_with_copy_memory());
     case protobufs::Transformation::TransformationCase::
+        kReplaceParameterWithGlobal:
+      return MakeUnique<TransformationReplaceParameterWithGlobal>(
+          message.replace_parameter_with_global());
+    case protobufs::Transformation::TransformationCase::
         kReplaceParamsWithStruct:
       return MakeUnique<TransformationReplaceParamsWithStruct>(
           message.replace_params_with_struct());
diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
new file mode 100644
index 0000000..ea84cf2
--- /dev/null
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.cpp
@@ -0,0 +1,232 @@
+// Copyright (c) 2020 Google LLC
+//
+// 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_replace_add_sub_mul_with_carrying_extended.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+namespace {
+const uint32_t kOpCompositeExtractIndexLowOrderBits = 0;
+const uint32_t kArithmeticInstructionIndexLeftInOperand = 0;
+const uint32_t kArithmeticInstructionIndexRightInOperand = 1;
+}  // namespace
+
+TransformationReplaceAddSubMulWithCarryingExtended::
+    TransformationReplaceAddSubMulWithCarryingExtended(
+        const spvtools::fuzz::protobufs::
+            TransformationReplaceAddSubMulWithCarryingExtended& message)
+    : message_(message) {}
+
+TransformationReplaceAddSubMulWithCarryingExtended::
+    TransformationReplaceAddSubMulWithCarryingExtended(uint32_t struct_fresh_id,
+                                                       uint32_t result_id) {
+  message_.set_struct_fresh_id(struct_fresh_id);
+  message_.set_result_id(result_id);
+}
+
+bool TransformationReplaceAddSubMulWithCarryingExtended::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  // |message_.struct_fresh_id| must be fresh.
+  if (!fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id())) {
+    return false;
+  }
+
+  // |message_.result_id| must refer to a suitable OpIAdd, OpISub or OpIMul
+  // instruction. The instruction must be defined.
+  auto instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+  if (instruction == nullptr) {
+    return false;
+  }
+  if (!TransformationReplaceAddSubMulWithCarryingExtended::
+          IsInstructionSuitable(ir_context, *instruction)) {
+    return false;
+  }
+
+  // The struct type for holding the intermediate result must exist in the
+  // module. The struct type is based on the operand type.
+  uint32_t operand_type_id = ir_context->get_def_use_mgr()
+                                 ->GetDef(instruction->GetSingleWordInOperand(
+                                     kArithmeticInstructionIndexLeftInOperand))
+                                 ->type_id();
+
+  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
+      ir_context, {operand_type_id, operand_type_id});
+  if (struct_type_id == 0) {
+    return false;
+  }
+  return true;
+}
+
+void TransformationReplaceAddSubMulWithCarryingExtended::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // |message_.struct_fresh_id| must be fresh.
+  assert(fuzzerutil::IsFreshId(ir_context, message_.struct_fresh_id()) &&
+         "|message_.struct_fresh_id| must be fresh");
+
+  // Get the signedness of an operand if it is an int or the signedness of a
+  // component if it is a vector.
+  auto type_id =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id())->type_id();
+  auto type = ir_context->get_type_mgr()->GetType(type_id);
+  bool operand_is_signed;
+  if (type->kind() == opt::analysis::Type::kVector) {
+    auto operand_type = type->AsVector()->element_type();
+    operand_is_signed = operand_type->AsInteger()->IsSigned();
+  } else {
+    operand_is_signed = type->AsInteger()->IsSigned();
+  }
+
+  auto original_instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.result_id());
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.struct_fresh_id());
+
+  // Determine the opcode of the new instruction that computes the result into a
+  // struct.
+  SpvOp new_instruction_opcode;
+
+  switch (original_instruction->opcode()) {
+    case SpvOpIAdd:
+      new_instruction_opcode = SpvOpIAddCarry;
+      break;
+    case SpvOpISub:
+      new_instruction_opcode = SpvOpISubBorrow;
+      break;
+    case SpvOpIMul:
+      if (!operand_is_signed) {
+        new_instruction_opcode = SpvOpUMulExtended;
+      } else {
+        new_instruction_opcode = SpvOpSMulExtended;
+      }
+      break;
+    default:
+      assert(false && "The instruction has an unsupported opcode.");
+      return;
+  }
+  // Get the type of struct type id holding the intermediate result based on the
+  // operand type.
+  uint32_t operand_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(original_instruction->GetSingleWordInOperand(
+              kArithmeticInstructionIndexLeftInOperand))
+          ->type_id();
+
+  uint32_t struct_type_id = fuzzerutil::MaybeGetStructType(
+      ir_context, {operand_type_id, operand_type_id});
+  // Avoid unused variables in release mode.
+  (void)struct_type_id;
+  assert(struct_type_id && "The struct type must exist in the module.");
+
+  // Insert the new instruction that computes the result into a struct before
+  // the |original_instruction|.
+  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, new_instruction_opcode, struct_type_id,
+      message_.struct_fresh_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID,
+            {original_instruction->GetSingleWordInOperand(
+                kArithmeticInstructionIndexLeftInOperand)}},
+           {SPV_OPERAND_TYPE_ID,
+            {original_instruction->GetSingleWordInOperand(
+                kArithmeticInstructionIndexRightInOperand)}}})));
+
+  // Insert the OpCompositeExtract after the added instruction. This instruction
+  // takes the first component of the struct which represents low-order bits of
+  // the operation. This is the original result.
+  original_instruction->InsertBefore(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpCompositeExtract, original_instruction->type_id(),
+      message_.result_id(),
+      opt::Instruction::OperandList(
+          {{SPV_OPERAND_TYPE_ID, {message_.struct_fresh_id()}},
+           {SPV_OPERAND_TYPE_LITERAL_INTEGER,
+            {kOpCompositeExtractIndexLowOrderBits}}})));
+
+  // Remove the original instruction.
+  ir_context->KillInst(original_instruction);
+
+  // We have modified the module so most analyzes are now invalid.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+bool TransformationReplaceAddSubMulWithCarryingExtended::IsInstructionSuitable(
+    opt::IRContext* ir_context, const opt::Instruction& instruction) {
+  auto instruction_opcode = instruction.opcode();
+
+  // Only instructions OpIAdd, OpISub, OpIMul are supported.
+  switch (instruction_opcode) {
+    case SpvOpIAdd:
+    case SpvOpISub:
+    case SpvOpIMul:
+      break;
+    default:
+      return false;
+  }
+  uint32_t operand_1_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(instruction.GetSingleWordInOperand(
+              kArithmeticInstructionIndexLeftInOperand))
+          ->type_id();
+
+  uint32_t operand_2_type_id =
+      ir_context->get_def_use_mgr()
+          ->GetDef(instruction.GetSingleWordInOperand(
+              kArithmeticInstructionIndexRightInOperand))
+          ->type_id();
+
+  uint32_t result_type_id = instruction.type_id();
+
+  // Both type ids of the operands and the result type ids must be equal.
+  if (operand_1_type_id != operand_2_type_id) {
+    return false;
+  }
+  if (operand_2_type_id != result_type_id) {
+    return false;
+  }
+
+  // In case of OpIAdd and OpISub, the type must be unsigned.
+  auto type = ir_context->get_type_mgr()->GetType(instruction.type_id());
+
+  switch (instruction_opcode) {
+    case SpvOpIAdd:
+    case SpvOpISub: {
+      // In case of OpIAdd and OpISub if the operand is a vector, the component
+      // type must be unsigned. Otherwise (if the operand is an int), the
+      // operand must be unsigned.
+      bool operand_is_signed =
+          type->AsVector()
+              ? type->AsVector()->element_type()->AsInteger()->IsSigned()
+              : type->AsInteger()->IsSigned();
+      if (operand_is_signed) {
+        return false;
+      }
+    } break;
+    default:
+      break;
+  }
+  return true;
+}
+
+protobufs::Transformation
+TransformationReplaceAddSubMulWithCarryingExtended::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_add_sub_mul_with_carrying_extended() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
new file mode 100644
index 0000000..db71779
--- /dev/null
+++ b/source/fuzz/transformation_replace_add_sub_mul_with_carrying_extended.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2020 Google LLC
+//
+// 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 SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H
+#define SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_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 TransformationReplaceAddSubMulWithCarryingExtended
+    : public Transformation {
+ public:
+  explicit TransformationReplaceAddSubMulWithCarryingExtended(
+      const protobufs::TransformationReplaceAddSubMulWithCarryingExtended&
+          message);
+
+  explicit TransformationReplaceAddSubMulWithCarryingExtended(
+      uint32_t struct_fresh_id, uint32_t result_id);
+
+  // - |message_.struct_fresh_id| must be fresh.
+  // - |message_.result_id| must refer to an OpIAdd or OpISub or OpIMul
+  //   instruction. In this instruction the result type id and the type ids of
+  //   the operands must be the same.
+  // - The type of struct holding the intermediate result must exists in the
+  //   module.
+  // - For OpIAdd, OpISub both operands must be unsigned.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // A transformation that replaces instructions OpIAdd, OpISub, OpIMul with
+  // pairs of instructions. The first one (OpIAddCarry, OpISubBorrow,
+  // OpUMulExtended, OpSMulExtended) computes the result into a struct. The
+  // second one extracts the appropriate component from the struct to yield the
+  // original result.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Checks if an OpIAdd, OpISub or OpIMul instruction can be used by the
+  // transformation.
+  bool static IsInstructionSuitable(opt::IRContext* ir_context,
+                                    const opt::Instruction& instruction);
+
+ private:
+  protobufs::TransformationReplaceAddSubMulWithCarryingExtended message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SPIRV_TOOLS_TRANSFORMATION_REPLACE_ADD_SUB_MUL_WITH_CARRYING_EXTENDED_H
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 6b3f72b..b4388d7 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -70,7 +70,7 @@
           transformation_permute_function_parameters_test.cpp
           transformation_permute_phi_operands_test.cpp
           transformation_push_id_through_variable_test.cpp
-          transformation_replace_parameter_with_global_test.cpp
+          transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
           transformation_replace_boolean_constant_with_constant_binary_test.cpp
           transformation_replace_copy_object_with_store_load_test.cpp
           transformation_replace_constant_with_uniform_test.cpp
@@ -78,6 +78,7 @@
           transformation_replace_id_with_synonym_test.cpp
           transformation_replace_linear_algebra_instruction_test.cpp
           transformation_replace_load_store_with_copy_memory_test.cpp
+          transformation_replace_parameter_with_global_test.cpp
           transformation_replace_params_with_struct_test.cpp
           transformation_set_function_control_test.cpp
           transformation_set_loop_control_test.cpp
diff --git a/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
new file mode 100644
index 0000000..258dc7f
--- /dev/null
+++ b/test/fuzz/transformation_replace_add_sub_mul_with_carrying_extended_test.cpp
@@ -0,0 +1,610 @@
+// Copyright (c) 2020 Google LLC
+//
+// 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_replace_add_sub_mul_with_carrying_extended.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableBasicChecks) {
+  // First conditions in IsApplicable() are checked.
+  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 %8 "i1"
+               OpName %10 "i2"
+               OpName %12 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 2
+         %11 = OpConstant %6 3
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %10
+         %14 = OpLoad %6 %8
+         %15 = OpSDiv %6 %13 %14
+               OpStore %12 %15
+               OpReturn
+               OpFunctionEnd
+    )";
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: |struct_fresh_id| must be fresh.
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(14, 15);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an instruction OpSDiv.
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(20, 15);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an nonexistent instruction.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(20, 21);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableDifferingSignedTypes) {
+  // Operand types and result types do not match. Not applicable to an operation
+  // on vectors with signed integers and operation on signed integers.
+  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 %8 "i1"
+               OpName %10 "i2"
+               OpName %16 "v1"
+               OpName %20 "v2"
+               OpName %25 "v3"
+               OpName %31 "u1"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 1
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %14 = OpTypeVector %6 3
+         %15 = OpTypePointer Function %14
+         %17 = OpConstant %6 0
+         %18 = OpConstant %6 2
+         %19 = OpConstantComposite %14 %17 %9 %18
+         %21 = OpConstant %6 3
+         %22 = OpConstant %6 4
+         %23 = OpConstant %6 5
+         %24 = OpConstantComposite %14 %21 %22 %23
+         %29 = OpTypeInt 32 0
+         %30 = OpTypePointer Function %29
+         %32 = OpConstant %29 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %16 = OpVariable %15 Function
+         %20 = OpVariable %15 Function
+         %25 = OpVariable %15 Function
+         %31 = OpVariable %30 Function
+               OpStore %8 %9
+         %11 = OpLoad %6 %8
+         %12 = OpLoad %6 %8
+         %13 = OpISub %6 %11 %12
+               OpStore %10 %13
+               OpStore %16 %19
+               OpStore %20 %24
+         %26 = OpLoad %14 %16
+         %27 = OpLoad %14 %20
+         %28 = OpIAdd %14 %26 %27
+               OpStore %25 %28
+               OpStore %31 %32
+         %40 = OpIMul %6 %32 %18
+         %41 = OpIAdd %6 %32 %32
+               OpReturn
+               OpFunctionEnd
+    )";
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  // Bad: The transformation cannot be applied to an instruction OpIMul that has
+  // different signedness of the types of operands.
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 40);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to an instruction OpIAdd that has
+  // different signedness of the result type than the signedness of the types of
+  // the operands.
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 41);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpIAdd of two
+  // vectors that have signed components.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 28);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpISub of two
+  // signed integers
+  auto transformation_bad_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 13);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     NotApplicableMissingStructTypes) {
+  // In all cases the required struct types are missing.
+  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 %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %18 = OpISub %6 %16 %17
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %21 = OpIMul %6 %19 %20
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %31 = OpIMul %22 %29 %30
+               OpStore %28 %31
+               OpReturn
+               OpFunctionEnd
+    )";
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto transformation_bad_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 15);
+  ASSERT_FALSE(
+      transformation_bad_1.IsApplicable(context.get(), transformation_context));
+
+  auto transformation_bad_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 18);
+  ASSERT_FALSE(
+      transformation_bad_2.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpIAdd of two
+  // vectors that have signed components.
+  auto transformation_bad_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 21);
+  ASSERT_FALSE(
+      transformation_bad_3.IsApplicable(context.get(), transformation_context));
+
+  // Bad: The transformation cannot be applied to the instruction OpISub of two
+  // signed integers
+  auto transformation_bad_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(50, 31);
+  ASSERT_FALSE(
+      transformation_bad_4.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceAddSubMulWithCarryingExtendedTest,
+     ApplicableScenarios) {
+  // In this test all of the transformations can be applied. The required struct
+  // types are provided.
+  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 %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+               OpName %34 "uv1"
+               OpName %36 "uv2"
+               OpName %39 "uv3"
+               OpName %51 "v1"
+               OpName %53 "v2"
+               OpName %56 "v3"
+               OpName %60 "pair_uint"
+               OpMemberName %60 0 "u_1"
+               OpMemberName %60 1 "u_2"
+               OpName %62 "p_uint"
+               OpName %63 "pair_uvec2"
+               OpMemberName %63 0 "uv_1"
+               OpMemberName %63 1 "uv_2"
+               OpName %65 "p_uvec2"
+               OpName %66 "pair_ivec2"
+               OpMemberName %66 0 "v_1"
+               OpMemberName %66 1 "v_2"
+               OpName %68 "p_ivec2"
+               OpName %69 "pair_int"
+               OpMemberName %69 0 "i_1"
+               OpMemberName %69 1 "i_2"
+               OpName %71 "p_int"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+         %32 = OpTypeVector %6 2
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %9 %11
+         %37 = OpConstant %6 3
+         %38 = OpConstantComposite %32 %11 %37
+         %49 = OpTypeVector %22 2
+         %50 = OpTypePointer Function %49
+         %52 = OpConstantComposite %49 %25 %27
+         %54 = OpConstant %22 3
+         %55 = OpConstantComposite %49 %27 %54
+         %60 = OpTypeStruct %6 %6
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %63 = OpTypeStruct %32 %32
+         %64 = OpTypePointer Private %63
+         %65 = OpVariable %64 Private
+         %66 = OpTypeStruct %49 %49
+         %67 = OpTypePointer Private %66
+         %68 = OpVariable %67 Private
+         %69 = OpTypeStruct %22 %22
+         %70 = OpTypePointer Private %69
+         %71 = OpVariable %70 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+         %34 = OpVariable %33 Function
+         %36 = OpVariable %33 Function
+         %39 = OpVariable %33 Function
+         %51 = OpVariable %50 Function
+         %53 = OpVariable %50 Function
+         %56 = OpVariable %50 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %15 = OpIAdd %6 %13 %14
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %18 = OpISub %6 %16 %17
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %21 = OpIMul %6 %19 %20
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %31 = OpIMul %22 %29 %30
+               OpStore %28 %31
+               OpStore %34 %35
+               OpStore %36 %38
+         %40 = OpLoad %32 %34
+         %41 = OpLoad %32 %36
+         %42 = OpIAdd %32 %40 %41
+               OpStore %39 %42
+         %43 = OpLoad %32 %34
+         %44 = OpLoad %32 %36
+         %45 = OpISub %32 %43 %44
+               OpStore %39 %45
+         %46 = OpLoad %32 %34
+         %47 = OpLoad %32 %36
+         %48 = OpIMul %32 %46 %47
+               OpStore %39 %48
+               OpStore %51 %52
+               OpStore %53 %55
+         %57 = OpLoad %49 %51
+         %58 = OpLoad %49 %53
+         %59 = OpIMul %49 %57 %58
+               OpStore %56 %59
+               OpReturn
+               OpFunctionEnd
+    )";
+  const auto env = SPV_ENV_UNIVERSAL_1_4;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto transformation_good_1 =
+      TransformationReplaceAddSubMulWithCarryingExtended(80, 15);
+  ASSERT_TRUE(transformation_good_1.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_1.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_2 =
+      TransformationReplaceAddSubMulWithCarryingExtended(81, 18);
+  ASSERT_TRUE(transformation_good_2.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_2.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_3 =
+      TransformationReplaceAddSubMulWithCarryingExtended(82, 21);
+  ASSERT_TRUE(transformation_good_3.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_3.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_4 =
+      TransformationReplaceAddSubMulWithCarryingExtended(83, 31);
+  ASSERT_TRUE(transformation_good_4.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_4.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_5 =
+      TransformationReplaceAddSubMulWithCarryingExtended(84, 42);
+  ASSERT_TRUE(transformation_good_5.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_5.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_6 =
+      TransformationReplaceAddSubMulWithCarryingExtended(85, 45);
+  ASSERT_TRUE(transformation_good_6.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_6.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_7 =
+      TransformationReplaceAddSubMulWithCarryingExtended(86, 48);
+  ASSERT_TRUE(transformation_good_7.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_7.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  auto transformation_good_8 =
+      TransformationReplaceAddSubMulWithCarryingExtended(87, 59);
+  ASSERT_TRUE(transformation_good_8.IsApplicable(context.get(),
+                                                 transformation_context));
+  transformation_good_8.Apply(context.get(), &transformation_context);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformation = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %4 "main"
+               OpName %8 "u1"
+               OpName %10 "u2"
+               OpName %12 "u3"
+               OpName %24 "i1"
+               OpName %26 "i2"
+               OpName %28 "i3"
+               OpName %34 "uv1"
+               OpName %36 "uv2"
+               OpName %39 "uv3"
+               OpName %51 "v1"
+               OpName %53 "v2"
+               OpName %56 "v3"
+               OpName %60 "pair_uint"
+               OpMemberName %60 0 "u_1"
+               OpMemberName %60 1 "u_2"
+               OpName %62 "p_uint"
+               OpName %63 "pair_uvec2"
+               OpMemberName %63 0 "uv_1"
+               OpMemberName %63 1 "uv_2"
+               OpName %65 "p_uvec2"
+               OpName %66 "pair_ivec2"
+               OpMemberName %66 0 "v_1"
+               OpMemberName %66 1 "v_2"
+               OpName %68 "p_ivec2"
+               OpName %69 "pair_int"
+               OpMemberName %69 0 "i_1"
+               OpMemberName %69 1 "i_2"
+               OpName %71 "p_int"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeInt 32 0
+          %7 = OpTypePointer Function %6
+          %9 = OpConstant %6 1
+         %11 = OpConstant %6 2
+         %22 = OpTypeInt 32 1
+         %23 = OpTypePointer Function %22
+         %25 = OpConstant %22 1
+         %27 = OpConstant %22 2
+         %32 = OpTypeVector %6 2
+         %33 = OpTypePointer Function %32
+         %35 = OpConstantComposite %32 %9 %11
+         %37 = OpConstant %6 3
+         %38 = OpConstantComposite %32 %11 %37
+         %49 = OpTypeVector %22 2
+         %50 = OpTypePointer Function %49
+         %52 = OpConstantComposite %49 %25 %27
+         %54 = OpConstant %22 3
+         %55 = OpConstantComposite %49 %27 %54
+         %60 = OpTypeStruct %6 %6
+         %61 = OpTypePointer Private %60
+         %62 = OpVariable %61 Private
+         %63 = OpTypeStruct %32 %32
+         %64 = OpTypePointer Private %63
+         %65 = OpVariable %64 Private
+         %66 = OpTypeStruct %49 %49
+         %67 = OpTypePointer Private %66
+         %68 = OpVariable %67 Private
+         %69 = OpTypeStruct %22 %22
+         %70 = OpTypePointer Private %69
+         %71 = OpVariable %70 Private
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+          %8 = OpVariable %7 Function
+         %10 = OpVariable %7 Function
+         %12 = OpVariable %7 Function
+         %24 = OpVariable %23 Function
+         %26 = OpVariable %23 Function
+         %28 = OpVariable %23 Function
+         %34 = OpVariable %33 Function
+         %36 = OpVariable %33 Function
+         %39 = OpVariable %33 Function
+         %51 = OpVariable %50 Function
+         %53 = OpVariable %50 Function
+         %56 = OpVariable %50 Function
+               OpStore %8 %9
+               OpStore %10 %11
+         %13 = OpLoad %6 %8
+         %14 = OpLoad %6 %10
+         %80 = OpIAddCarry %60 %13 %14
+         %15 = OpCompositeExtract %6 %80 0
+               OpStore %12 %15
+         %16 = OpLoad %6 %8
+         %17 = OpLoad %6 %10
+         %81 = OpISubBorrow %60 %16 %17
+         %18 = OpCompositeExtract %6 %81 0
+               OpStore %12 %18
+         %19 = OpLoad %6 %8
+         %20 = OpLoad %6 %10
+         %82 = OpUMulExtended %60 %19 %20
+         %21 = OpCompositeExtract %6 %82 0
+               OpStore %12 %21
+               OpStore %24 %25
+               OpStore %26 %27
+         %29 = OpLoad %22 %24
+         %30 = OpLoad %22 %26
+         %83 = OpSMulExtended %69 %29 %30
+         %31 = OpCompositeExtract %22 %83 0
+               OpStore %28 %31
+               OpStore %34 %35
+               OpStore %36 %38
+         %40 = OpLoad %32 %34
+         %41 = OpLoad %32 %36
+         %84 = OpIAddCarry %63 %40 %41
+         %42 = OpCompositeExtract %32 %84 0
+               OpStore %39 %42
+         %43 = OpLoad %32 %34
+         %44 = OpLoad %32 %36
+         %85 = OpISubBorrow %63 %43 %44
+         %45 = OpCompositeExtract %32 %85 0
+               OpStore %39 %45
+         %46 = OpLoad %32 %34
+         %47 = OpLoad %32 %36
+         %86 = OpUMulExtended %63 %46 %47
+         %48 = OpCompositeExtract %32 %86 0
+               OpStore %39 %48
+               OpStore %51 %52
+               OpStore %53 %55
+         %57 = OpLoad %49 %51
+         %58 = OpLoad %49 %53
+         %87 = OpSMulExtended %66 %57 %58
+         %59 = OpCompositeExtract %49 %87 0
+               OpStore %56 %59
+               OpReturn
+               OpFunctionEnd
+  )";
+  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools