spirv-fuzz: Pass to replace int operands with ints of opposite signedness (#3612)

This PR introduces a new fuzzer pass, which:

- finds all integer vectors or constants
- finds or creates the corresponding constants with opposite
  signedness
- records such constants as synonyms of the first ones
- replaces the usages of the original constants with the new ones
  if allowed

Fixes #2677.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 16075c8..e8c9fcb 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -65,6 +65,7 @@
         fuzzer_pass_copy_objects.h
         fuzzer_pass_donate_modules.h
         fuzzer_pass_invert_comparison_operators.h
+        fuzzer_pass_interchange_signedness_of_integer_operands.h
         fuzzer_pass_interchange_zero_like_constants.h
         fuzzer_pass_merge_blocks.h
         fuzzer_pass_obfuscate_constants.h
@@ -194,6 +195,7 @@
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_donate_modules.cpp
         fuzzer_pass_invert_comparison_operators.cpp
+        fuzzer_pass_interchange_signedness_of_integer_operands.cpp
         fuzzer_pass_interchange_zero_like_constants.cpp
         fuzzer_pass_merge_blocks.cpp
         fuzzer_pass_obfuscate_constants.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index f549590..ae4332b 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -46,6 +46,7 @@
 #include "source/fuzz/fuzzer_pass_construct_composites.h"
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
+#include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
 #include "source/fuzz/fuzzer_pass_interchange_zero_like_constants.h"
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
 #include "source/fuzz/fuzzer_pass_merge_blocks.h"
@@ -335,6 +336,9 @@
   MaybeAddPass<FuzzerPassAddNoContractionDecorations>(
       &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
+  MaybeAddPass<FuzzerPassInterchangeSignednessOfIntegerOperands>(
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
+      transformation_sequence_out);
   MaybeAddPass<FuzzerPassInterchangeZeroLikeConstants>(
       &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 3f1fe16..dacdc58 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -67,6 +67,8 @@
     {50, 95};
 const std::pair<uint32_t, uint32_t> kChanceOfInterchangingZeroLikeConstants = {
     10, 90};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfInterchangingSignednessOfIntegerOperands = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfInvertingComparisonOperators = {
     20, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfMakingDonorLivesafe = {40, 60};
@@ -197,6 +199,8 @@
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
   chance_of_going_deeper_when_making_access_chain_ =
       ChooseBetweenMinAndMax(kChanceOfGoingDeeperWhenMakingAccessChain);
+  chance_of_interchanging_signedness_of_integer_operands_ =
+      ChooseBetweenMinAndMax(kChanceOfInterchangingSignednessOfIntegerOperands);
   chance_of_interchanging_zero_like_constants_ =
       ChooseBetweenMinAndMax(kChanceOfInterchangingZeroLikeConstants);
   chance_of_inverting_comparison_operators_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 3376c1e..24aad0c 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -186,6 +186,9 @@
   uint32_t GetChanceOfGoingDeeperWhenMakingAccessChain() {
     return chance_of_going_deeper_when_making_access_chain_;
   }
+  uint32_t GetChanceOfInterchangingSignednessOfIntegerOperands() {
+    return chance_of_interchanging_signedness_of_integer_operands_;
+  }
   uint32_t GetChanceOfInterchangingZeroLikeConstants() {
     return chance_of_interchanging_zero_like_constants_;
   }
@@ -353,6 +356,7 @@
   uint32_t chance_of_copying_object_;
   uint32_t chance_of_donating_additional_module_;
   uint32_t chance_of_going_deeper_when_making_access_chain_;
+  uint32_t chance_of_interchanging_signedness_of_integer_operands_;
   uint32_t chance_of_interchanging_zero_like_constants_;
   uint32_t chance_of_inverting_comparison_operators_;
   uint32_t chance_of_making_donor_livesafe_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index ebd88d0..8d1b28c 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -17,6 +17,7 @@
 #include <set>
 
 #include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
 #include "source/fuzz/instruction_descriptor.h"
 #include "source/fuzz/transformation_add_constant_boolean.h"
 #include "source/fuzz/transformation_add_constant_composite.h"
@@ -514,5 +515,23 @@
   }
 }
 
+void FuzzerPass::MaybeAddUseToReplace(
+    opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
+    std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
+        uses_to_replace) {
+  // Only consider this use if it is in a block
+  if (!GetIRContext()->get_instr_block(use_inst)) {
+    return;
+  }
+
+  // Get the index of the operand restricted to input operands.
+  uint32_t in_operand_index =
+      fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
+  auto id_use_descriptor =
+      MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
+  uses_to_replace->emplace_back(
+      std::make_pair(id_use_descriptor, replacement_id));
+}
+
 }  // namespace fuzz
 }  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 4731b74..2777f24 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -273,6 +273,16 @@
   uint32_t FindOrCreateZeroConstant(uint32_t scalar_or_composite_type_id,
                                     bool is_irrelevant);
 
+  // Adds a pair (id_use_descriptor, |replacement_id|) to the vector
+  // |uses_to_replace|, where id_use_descriptor is the id use descriptor
+  // representing the usage of an id in the |use_inst| instruction, at operand
+  // index |use_index|, only if the instruction is in a basic block.
+  // If the instruction is not in a basic block, it does nothing.
+  void MaybeAddUseToReplace(
+      opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
+      std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
+          uses_to_replace);
+
  private:
   opt::IRContext* ir_context_;
   TransformationContext* transformation_context_;
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
new file mode 100644
index 0000000..6c3aa7b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.cpp
@@ -0,0 +1,149 @@
+// 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 "fuzzer_pass_interchange_signedness_of_integer_operands.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/id_use_descriptor.h"
+#include "source/fuzz/transformation_record_synonymous_constants.h"
+#include "source/fuzz/transformation_replace_id_with_synonym.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassInterchangeSignednessOfIntegerOperands::
+    FuzzerPassInterchangeSignednessOfIntegerOperands(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassInterchangeSignednessOfIntegerOperands::
+    ~FuzzerPassInterchangeSignednessOfIntegerOperands() = default;
+
+void FuzzerPassInterchangeSignednessOfIntegerOperands::Apply() {
+  // Make vector keeping track of all the uses we want to replace.
+  // This is a vector of pairs, where the first element is an id use descriptor
+  // identifying the use of a constant id and the second is the id that should
+  // be used to replace it.
+  std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>> uses_to_replace;
+
+  for (auto constant : GetIRContext()->GetConstants()) {
+    uint32_t constant_id = constant->result_id();
+
+    // We want to record the synonymity of an integer constant with another
+    // constant with opposite signedness, and this can only be done if they are
+    // not irrelevant.
+    if (GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+            constant_id)) {
+      continue;
+    }
+
+    uint32_t toggled_id =
+        FindOrCreateToggledIntegerConstant(constant->result_id());
+    if (!toggled_id) {
+      // Not an integer constant
+      continue;
+    }
+
+    assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
+               toggled_id) &&
+           "FindOrCreateToggledConstant can't produce an irrelevant id");
+
+    // Record synonymous constants
+    ApplyTransformation(
+        TransformationRecordSynonymousConstants(constant_id, toggled_id));
+
+    // Find all the uses of the constant and, for each, probabilistically
+    // decide whether to replace it.
+    GetIRContext()->get_def_use_mgr()->ForEachUse(
+        constant_id,
+        [this, toggled_id, &uses_to_replace](opt::Instruction* use_inst,
+                                             uint32_t use_index) -> void {
+          if (GetFuzzerContext()->ChoosePercentage(
+                  GetFuzzerContext()
+                      ->GetChanceOfInterchangingSignednessOfIntegerOperands())) {
+            MaybeAddUseToReplace(use_inst, use_index, toggled_id,
+                                 &uses_to_replace);
+          }
+        });
+  }
+
+  // Replace the ids if it is allowed.
+  for (auto use_to_replace : uses_to_replace) {
+    MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
+        use_to_replace.first, use_to_replace.second));
+  }
+}
+
+uint32_t FuzzerPassInterchangeSignednessOfIntegerOperands::
+    FindOrCreateToggledIntegerConstant(uint32_t id) {
+  auto constant = GetIRContext()->get_constant_mgr()->FindDeclaredConstant(id);
+
+  // This pass only toggles integer constants.
+  if (!constant->AsIntConstant() &&
+      (!constant->AsVectorConstant() ||
+       !constant->AsVectorConstant()->component_type()->AsInteger())) {
+    return 0;
+  }
+
+  if (auto integer = constant->AsIntConstant()) {
+    auto type = integer->type()->AsInteger();
+
+    // Find or create and return the toggled constant.
+    return FindOrCreateIntegerConstant(std::vector<uint32_t>(integer->words()),
+                                       type->width(), !type->IsSigned(), false);
+  }
+
+  // The constant is an integer vector.
+
+  // Find the component type.
+  auto component_type =
+      constant->AsVectorConstant()->component_type()->AsInteger();
+
+  // Find or create the toggled component type.
+  uint32_t toggled_component_type = FindOrCreateIntegerType(
+      component_type->width(), !component_type->IsSigned());
+
+  // Get the information about the toggled components. We need to extract this
+  // information now because the analyses might be invalidated, which would make
+  // the constant and component_type variables invalid.
+  std::vector<std::vector<uint32_t>> component_words;
+
+  for (auto component : constant->AsVectorConstant()->GetComponents()) {
+    component_words.push_back(component->AsIntConstant()->words());
+  }
+  uint32_t width = component_type->width();
+  bool is_signed = !component_type->IsSigned();
+
+  std::vector<uint32_t> toggled_components;
+
+  // Find or create the toggled components.
+  for (auto words : component_words) {
+    toggled_components.push_back(
+        FindOrCreateIntegerConstant(words, width, is_signed, false));
+  }
+
+  // Find or create the required toggled vector type.
+  uint32_t toggled_type = FindOrCreateVectorType(
+      toggled_component_type, (uint32_t)toggled_components.size());
+
+  // Find or create and return the toggled vector constant.
+  return FindOrCreateCompositeConstant(toggled_components, toggled_type, false);
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
new file mode 100644
index 0000000..4824218
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h
@@ -0,0 +1,51 @@
+// 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 SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_
+#define SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A pass that:
+// - Finds all the integer constant (scalar and vector) definitions in the
+//   module and adds the definitions of the integer with the same data words but
+//   opposite signedness. If the synonym is already in the module, it does not
+//   add a new one.
+// - For each use of an integer constant where its signedness does not matter,
+// decides whether to change it to the id of the toggled constant.
+class FuzzerPassInterchangeSignednessOfIntegerOperands : public FuzzerPass {
+ public:
+  FuzzerPassInterchangeSignednessOfIntegerOperands(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassInterchangeSignednessOfIntegerOperands() override;
+
+  void Apply() override;
+
+ private:
+  // Given the id of an integer constant (scalar or vector), it finds or creates
+  // the corresponding toggled constant (the integer with the same data words
+  // but opposite signedness). Returns the id of the toggled instruction if the
+  // constant is an integer scalar or vector, 0 otherwise.
+  uint32_t FindOrCreateToggledIntegerConstant(uint32_t id);
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_INTERCHANGE_SIGNEDNESS_OF_INTEGER_OPERANDS_
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
index 727132e..8bd670f 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.cpp
@@ -57,24 +57,6 @@
   return 0;
 }
 
-void FuzzerPassInterchangeZeroLikeConstants::MaybeAddUseToReplace(
-    opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
-    std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
-        uses_to_replace) {
-  // Only consider this use if it is in a block
-  if (!GetIRContext()->get_instr_block(use_inst)) {
-    return;
-  }
-
-  // Get the index of the operand restricted to input operands.
-  uint32_t in_operand_index =
-      fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
-  auto id_use_descriptor =
-      MakeIdUseDescriptorFromUse(GetIRContext(), use_inst, in_operand_index);
-  uses_to_replace->emplace_back(
-      std::make_pair(id_use_descriptor, replacement_id));
-}
-
 void FuzzerPassInterchangeZeroLikeConstants::Apply() {
   // Make vector keeping track of all the uses we want to replace.
   // This is a vector of pairs, where the first element is an id use descriptor
@@ -118,7 +100,7 @@
         });
   }
 
-  // Replace the ids
+  // Replace the ids if it is allowed.
   for (auto use_to_replace : uses_to_replace) {
     MaybeApplyTransformation(TransformationReplaceIdWithSynonym(
         use_to_replace.first, use_to_replace.second));
diff --git a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
index 4fcc44e..4ea990a 100644
--- a/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
+++ b/source/fuzz/fuzzer_pass_interchange_zero_like_constants.h
@@ -46,16 +46,6 @@
   // Returns the id of the toggled instruction if the constant is zero-like,
   // 0 otherwise.
   uint32_t FindOrCreateToggledConstant(opt::Instruction* declaration);
-
-  // Given an id use (described by an instruction and an index) and an id with
-  // which the original one should be replaced, adds a pair (with the elements
-  // being the corresponding id use descriptor and the replacement id) to
-  // |uses_to_replace| if the use is in an instruction block, otherwise does
-  // nothing.
-  void MaybeAddUseToReplace(
-      opt::Instruction* use_inst, uint32_t use_index, uint32_t replacement_id,
-      std::vector<std::pair<protobufs::IdUseDescriptor, uint32_t>>*
-          uses_to_replace);
 };
 
 }  // namespace fuzz