spirv-fuzz: Add image sample unused components transformation (#3439)

Fixes #3375.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 7bd9ff2..b54b1fb 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -46,6 +46,7 @@
         fuzzer_pass_add_equation_instructions.h
         fuzzer_pass_add_function_calls.h
         fuzzer_pass_add_global_variables.h
+        fuzzer_pass_add_image_sample_unused_components.h
         fuzzer_pass_add_loads.h
         fuzzer_pass_add_local_variables.h
         fuzzer_pass_add_no_contraction_decorations.h
@@ -96,6 +97,7 @@
         transformation_add_function.h
         transformation_add_global_undef.h
         transformation_add_global_variable.h
+        transformation_add_image_sample_unused_components.h
         transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
         transformation_add_parameter.h
@@ -158,6 +160,7 @@
         fuzzer_pass_add_equation_instructions.cpp
         fuzzer_pass_add_function_calls.cpp
         fuzzer_pass_add_global_variables.cpp
+        fuzzer_pass_add_image_sample_unused_components.cpp
         fuzzer_pass_add_loads.cpp
         fuzzer_pass_add_local_variables.cpp
         fuzzer_pass_add_no_contraction_decorations.cpp
@@ -207,6 +210,7 @@
         transformation_add_function.cpp
         transformation_add_global_undef.cpp
         transformation_add_global_variable.cpp
+        transformation_add_image_sample_unused_components.cpp
         transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
         transformation_add_parameter.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 6cb6202..f08ce04 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -30,6 +30,7 @@
 #include "source/fuzz/fuzzer_pass_add_equation_instructions.h"
 #include "source/fuzz/fuzzer_pass_add_function_calls.h"
 #include "source/fuzz/fuzzer_pass_add_global_variables.h"
+#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
 #include "source/fuzz/fuzzer_pass_add_loads.h"
 #include "source/fuzz/fuzzer_pass_add_local_variables.h"
 #include "source/fuzz/fuzzer_pass_add_no_contraction_decorations.h"
@@ -224,6 +225,9 @@
     MaybeAddPass<FuzzerPassAddGlobalVariables>(
         &passes, ir_context.get(), &transformation_context, &fuzzer_context,
         transformation_sequence_out);
+    MaybeAddPass<FuzzerPassAddImageSampleUnusedComponents>(
+        &passes, ir_context.get(), &transformation_context, &fuzzer_context,
+        transformation_sequence_out);
     MaybeAddPass<FuzzerPassAddLoads>(&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 63aef85..5d4096b 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -34,6 +34,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfAddingEquationInstruction = {5,
                                                                           90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingGlobalVariable = {20, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfAddingImageSampleUnusedComponents =
+    {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingLoad = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingLocalVariable = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfAddingMatrixType = {20, 70};
@@ -137,6 +139,8 @@
   chance_of_adding_global_variable_ =
       ChooseBetweenMinAndMax(kChanceOfAddingGlobalVariable);
   chance_of_adding_load_ = ChooseBetweenMinAndMax(kChanceOfAddingLoad);
+  chance_of_adding_image_sample_unused_components_ =
+      ChooseBetweenMinAndMax(kChanceOfAddingImageSampleUnusedComponents);
   chance_of_adding_local_variable_ =
       ChooseBetweenMinAndMax(kChanceOfAddingLocalVariable);
   chance_of_adding_matrix_type_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 2bd4634..edf27a2 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -128,6 +128,9 @@
   uint32_t GetChanceOfAddingGlobalVariable() {
     return chance_of_adding_global_variable_;
   }
+  uint32_t GetChanceOfAddingImageSampleUnusedComponents() {
+    return chance_of_adding_image_sample_unused_components_;
+  }
   uint32_t GetChanceOfAddingLoad() { return chance_of_adding_load_; }
   uint32_t GetChanceOfAddingLocalVariable() {
     return chance_of_adding_local_variable_;
@@ -268,6 +271,11 @@
     // Ensure that the array size is non-zero.
     return random_generator_->RandomUint32(max_new_array_size_limit_ - 1) + 1;
   }
+  uint32_t GetRandomUnusedComponentCountForImageSample(
+      uint32_t max_unused_component_count) {
+    // Ensure that the number of unused components is non-zero.
+    return random_generator_->RandomUint32(max_unused_component_count) + 1;
+  }
   bool GoDeeperInConstantObfuscation(uint32_t depth) {
     return go_deeper_in_constant_obfuscation_(depth, random_generator_);
   }
@@ -289,6 +297,7 @@
   uint32_t chance_of_adding_dead_continue_;
   uint32_t chance_of_adding_equation_instruction_;
   uint32_t chance_of_adding_global_variable_;
+  uint32_t chance_of_adding_image_sample_unused_components_;
   uint32_t chance_of_adding_load_;
   uint32_t chance_of_adding_local_variable_;
   uint32_t chance_of_adding_matrix_type_;
diff --git a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
new file mode 100644
index 0000000..01fd282
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.cpp
@@ -0,0 +1,200 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_add_image_sample_unused_components.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_add_image_sample_unused_components.h"
+#include "source/fuzz/transformation_composite_construct.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassAddImageSampleUnusedComponents::
+    FuzzerPassAddImageSampleUnusedComponents(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassAddImageSampleUnusedComponents::
+    ~FuzzerPassAddImageSampleUnusedComponents() = default;
+
+void FuzzerPassAddImageSampleUnusedComponents::Apply() {
+  // SPIR-V module to help understand the transformation.
+  //
+  //       OpCapability Shader
+  //  %1 = OpExtInstImport "GLSL.std.450"
+  //       OpMemoryModel Logical GLSL450
+  //       OpEntryPoint Fragment %15 "main" %12 %14
+  //       OpExecutionMode %15 OriginUpperLeft
+  //
+  // ; Decorations
+  //        OpDecorate %12 Location 0 ; Input color variable location
+  //        OpDecorate %13 DescriptorSet 0 ; Image coordinate variable
+  //        descriptor set OpDecorate %13 Binding 0 ; Image coordinate
+  //        variable binding OpDecorate %14 Location 0 ; Fragment color
+  //        variable location
+  //
+  // ; Types
+  //  %2 = OpTypeVoid
+  //  %3 = OpTypeFunction %2
+  //  %4 = OpTypeFloat 32
+  //  %5 = OpTypeVector %4 2
+  //  %6 = OpTypeVector %4 4
+  //  %7 = OpTypeImage %4 2D 0 0 0 1 Rgba32f
+  //  %8 = OpTypeSampledImage %7
+  //  %9 = OpTypePointer Input %5
+  // %10 = OpTypePointer UniformConstant %8
+  // %11 = OpTypePointer Output %6
+  //
+  // ; Variables
+  // %12 = OpVariable %9 Input ; Input image coordinate variable
+  // %13 = OpVariable %10 UniformConstant ; Image variable
+  // %14 = OpVariable %11 Output ; Fragment color variable
+  //
+  // ; main function
+  // %15 = OpFunction %2 None %3
+  // %16 = OpLabel
+  // %17 = OpLoad %5 %12
+  // %18 = OpLoad %8 %13
+  // %19 = OpImageSampleImplicitLod %6 %18 %17
+  //       OpStore %14 %19
+  //       OpReturn
+  //       OpFunctionEnd
+
+  GetIRContext()->module()->ForEachInst([this](opt::Instruction* instruction) {
+    // |instruction| %19 = OpImageSampleImplicitLod %6 %18 %17
+    if (!spvOpcodeIsImageSample(instruction->opcode())) {
+      return;
+    }
+
+    if (!GetFuzzerContext()->ChoosePercentage(
+            GetFuzzerContext()
+                ->GetChanceOfAddingImageSampleUnusedComponents())) {
+      return;
+    }
+
+    // Gets image sample coordinate information.
+    // |coordinate_instruction| %17 = OpLoad %5 %12
+    uint32_t coordinate_id = instruction->GetSingleWordInOperand(1);
+    auto coordinate_instruction =
+        GetIRContext()->get_def_use_mgr()->GetDef(coordinate_id);
+    auto coordinate_type = GetIRContext()->get_type_mgr()->GetType(
+        coordinate_instruction->type_id());
+
+    // If the coordinate is a 4-dimensional vector, then no unused components
+    // may be added.
+    if (coordinate_type->AsVector() &&
+        coordinate_type->AsVector()->element_count() == 4) {
+      return;
+    }
+
+    // If the coordinate is a scalar, then at most 3 unused components may be
+    // added. If the coordinate is a vector, then the maximum number of unused
+    // components depends on the vector size.
+    // For the sample module, the coordinate type instruction is %5 =
+    // OpTypeVector %4 2, thus |max_unused_component_count| = 4 - 2 = 2.
+    uint32_t max_unused_component_count =
+        coordinate_type->AsInteger() || coordinate_type->AsFloat()
+            ? 3
+            : 4 - coordinate_type->AsVector()->element_count();
+
+    // |unused_component_count| may be 1 or 2.
+    uint32_t unused_component_count =
+        GetFuzzerContext()->GetRandomUnusedComponentCountForImageSample(
+            max_unused_component_count);
+
+    // Gets a type for the zero-unused components.
+    uint32_t zero_constant_type_id;
+    switch (unused_component_count) {
+      case 1:
+        // If the coordinate is an integer or float, then the unused components
+        // type is the same as the coordinate. If the coordinate is a vector,
+        // then the unused components type is the same as the vector components
+        // type.
+        zero_constant_type_id =
+            coordinate_type->AsInteger() || coordinate_type->AsFloat()
+                ? coordinate_instruction->type_id()
+                : GetIRContext()->get_type_mgr()->GetId(
+                      coordinate_type->AsVector()->element_type());
+        break;
+      case 2:
+      case 3:
+        // If the coordinate is an integer or float, then the unused components
+        // type is the same as the coordinate. If the coordinate is a vector,
+        // then the unused components type is the same as the coordinate
+        // components type.
+        // |zero_constant_type_id| %5 = OpTypeVector %4 2
+        zero_constant_type_id =
+            coordinate_type->AsInteger() || coordinate_type->AsFloat()
+                ? FindOrCreateVectorType(coordinate_instruction->type_id(),
+                                         unused_component_count)
+                : FindOrCreateVectorType(
+                      GetIRContext()->get_type_mgr()->GetId(
+                          coordinate_type->AsVector()->element_type()),
+                      unused_component_count);
+        break;
+      default:
+        assert(false && "Should be unreachable.");
+        zero_constant_type_id = 0;
+        break;
+    }
+
+    // Gets |coordinate_type| again because the module may have changed due to
+    // the use of FindOrCreateVectorType above.
+    coordinate_type = GetIRContext()->get_type_mgr()->GetType(
+        coordinate_instruction->type_id());
+
+    // If the new vector type with unused components does not exist, then create
+    // it. |coordinate_with_unused_components_type_id| %6 = OpTypeVector %4 4
+    uint32_t coordinate_with_unused_components_type_id =
+        coordinate_type->AsInteger() || coordinate_type->AsFloat()
+            ? FindOrCreateVectorType(coordinate_instruction->type_id(),
+                                     1 + unused_component_count)
+            : FindOrCreateVectorType(
+                  GetIRContext()->get_type_mgr()->GetId(
+                      coordinate_type->AsVector()->element_type()),
+                  coordinate_type->AsVector()->element_count() +
+                      unused_component_count);
+
+    // Inserts an OpCompositeConstruct instruction which
+    // represents the coordinate with unused components.
+    // |coordinate_with_unused_components_id|
+    // %22 = OpCompositeConstruct %6 %17 %21
+    uint32_t coordinate_with_unused_components_id =
+        GetFuzzerContext()->GetFreshId();
+    ApplyTransformation(TransformationCompositeConstruct(
+        coordinate_with_unused_components_type_id,
+        {coordinate_instruction->result_id(),
+         // FindOrCreateZeroConstant
+         // %20 = OpConstant %4 0
+         // %21 = OpConstantComposite %5 %20 %20
+         FindOrCreateZeroConstant(zero_constant_type_id)},
+        MakeInstructionDescriptor(GetIRContext(), instruction),
+        coordinate_with_unused_components_id));
+
+    // Tries to add unused components to the image sample coordinate.
+    // %19 = OpImageSampleImplicitLod %6 %18 %22
+    ApplyTransformation(TransformationAddImageSampleUnusedComponents(
+        coordinate_with_unused_components_id,
+        MakeInstructionDescriptor(GetIRContext(), instruction)));
+  });
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h
new file mode 100644
index 0000000..26374c3
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_add_image_sample_unused_components.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass searches for image sample instructions in the module and
+// randomly applies the transformation to add unused components to the image
+// sample coordinate.
+class FuzzerPassAddImageSampleUnusedComponents : public FuzzerPass {
+ public:
+  FuzzerPassAddImageSampleUnusedComponents(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassAddImageSampleUnusedComponents();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index b913d02..ce95f97 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -383,6 +383,7 @@
     TransformationAddParameter add_parameter = 52;
     TransformationAddCopyMemory add_copy_memory = 53;
     TransformationInvertComparisonOperator invert_comparison_operator = 54;
+    TransformationAddImageSampleUnusedComponents add_image_sample_unused_components = 55;
     // Add additional option using the next available number.
   }
 }
@@ -616,6 +617,18 @@
 
 }
 
+message TransformationAddImageSampleUnusedComponents {
+
+  // A transformation that adds unused components to an image sample coordinate.
+
+  // An vector id with the original coordinate and the unused components.
+  uint32 coordinate_with_unused_components_id = 1;
+
+  // A descriptor for an image sample instruction.
+  InstructionDescriptor instruction_descriptor = 2;
+
+}
+
 message TransformationAddLocalVariable {
 
   // Adds a local variable of the given type (which must be a pointer with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 26c93ef..29960e4 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -29,6 +29,7 @@
 #include "source/fuzz/transformation_add_function.h"
 #include "source/fuzz/transformation_add_global_undef.h"
 #include "source/fuzz/transformation_add_global_variable.h"
+#include "source/fuzz/transformation_add_image_sample_unused_components.h"
 #include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
 #include "source/fuzz/transformation_add_parameter.h"
@@ -112,6 +113,10 @@
     case protobufs::Transformation::TransformationCase::kAddGlobalVariable:
       return MakeUnique<TransformationAddGlobalVariable>(
           message.add_global_variable());
+    case protobufs::Transformation::TransformationCase::
+        kAddImageSampleUnusedComponents:
+      return MakeUnique<TransformationAddImageSampleUnusedComponents>(
+          message.add_image_sample_unused_components());
     case protobufs::Transformation::TransformationCase::kAddLocalVariable:
       return MakeUnique<TransformationAddLocalVariable>(
           message.add_local_variable());
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.cpp b/source/fuzz/transformation_add_image_sample_unused_components.cpp
new file mode 100644
index 0000000..1be1d43
--- /dev/null
+++ b/source/fuzz/transformation_add_image_sample_unused_components.cpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_image_sample_unused_components.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddImageSampleUnusedComponents::
+    TransformationAddImageSampleUnusedComponents(
+        const spvtools::fuzz::protobufs::
+            TransformationAddImageSampleUnusedComponents& message)
+    : message_(message) {}
+
+TransformationAddImageSampleUnusedComponents::
+    TransformationAddImageSampleUnusedComponents(
+        uint32_t coordinate_with_unused_components_id,
+        const protobufs::InstructionDescriptor& instruction_descriptor) {
+  message_.set_coordinate_with_unused_components_id(
+      coordinate_with_unused_components_id);
+  *message_.mutable_instruction_descriptor() = instruction_descriptor;
+}
+
+bool TransformationAddImageSampleUnusedComponents::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  auto image_sample_instruction =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+
+  // The image sample instruction must be defined.
+  if (image_sample_instruction == nullptr) {
+    return false;
+  }
+
+  // The instruction must be an image sample instruction.
+  if (!spvOpcodeIsImageSample(image_sample_instruction->opcode())) {
+    return false;
+  }
+
+  uint32_t coordinate_id = image_sample_instruction->GetSingleWordInOperand(1);
+  auto coordinate_instruction =
+      ir_context->get_def_use_mgr()->GetDef(coordinate_id);
+  auto coordinate_type =
+      ir_context->get_type_mgr()->GetType(coordinate_instruction->type_id());
+
+  // It must be possible to add unused components.
+  if (coordinate_type->AsVector() &&
+      coordinate_type->AsVector()->element_count() == 4) {
+    return false;
+  }
+
+  auto coordinate_with_unused_components_instruction =
+      ir_context->get_def_use_mgr()->GetDef(
+          message_.coordinate_with_unused_components_id());
+
+  // The coordinate with unused components instruction must be defined.
+  if (coordinate_with_unused_components_instruction == nullptr) {
+    return false;
+  }
+
+  // It must be an OpCompositeConstruct instruction such that it can be checked
+  // that the original components are present.
+  if (coordinate_with_unused_components_instruction->opcode() !=
+      SpvOpCompositeConstruct) {
+    return false;
+  }
+
+  // The first constituent must be the original coordinate.
+  if (coordinate_with_unused_components_instruction->GetSingleWordInOperand(
+          0) != coordinate_id) {
+    return false;
+  }
+
+  auto coordinate_with_unused_components_type =
+      ir_context->get_type_mgr()->GetType(
+          coordinate_with_unused_components_instruction->type_id());
+
+  // |coordinate_with_unused_components_type| must be a vector.
+  if (!coordinate_with_unused_components_type->AsVector()) {
+    return false;
+  }
+
+  return true;
+}
+
+void TransformationAddImageSampleUnusedComponents::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  // Sets the coordinate operand.
+  auto image_sample_instruction =
+      FindInstruction(message_.instruction_descriptor(), ir_context);
+  image_sample_instruction->SetInOperand(
+      1, {message_.coordinate_with_unused_components_id()});
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationAddImageSampleUnusedComponents::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_image_sample_unused_components() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_image_sample_unused_components.h b/source/fuzz/transformation_add_image_sample_unused_components.h
new file mode 100644
index 0000000..7493481
--- /dev/null
+++ b/source/fuzz/transformation_add_image_sample_unused_components.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SOURCE_FUZZ_TRANSFORMATION_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_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 TransformationAddImageSampleUnusedComponents : public Transformation {
+ public:
+  explicit TransformationAddImageSampleUnusedComponents(
+      const protobufs::TransformationAddImageSampleUnusedComponents& message);
+
+  TransformationAddImageSampleUnusedComponents(
+      uint32_t coordinate_with_unused_components_id,
+      const protobufs::InstructionDescriptor& instruction_descriptor);
+
+  // - |coordinate_with_unused_components_id| must identify a vector such that
+  //   the first components match the components of the image sample coordinate.
+  // - |message_.instruction_descriptor| must identify an image sample
+  //   instruction
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Add unused components to an image sample coordinate by replacing the
+  // coordinate with |coordinate_with_unused_components_id|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationAddImageSampleUnusedComponents message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_IMAGE_SAMPLE_UNUSED_COMPONENTS_H_
diff --git a/source/opcode.cpp b/source/opcode.cpp
index 079def6..0a7a95d 100644
--- a/source/opcode.cpp
+++ b/source/opcode.cpp
@@ -666,6 +666,26 @@
   }
 }
 
+bool spvOpcodeIsImageSample(const SpvOp opcode) {
+  switch (opcode) {
+    case SpvOpImageSampleImplicitLod:
+    case SpvOpImageSampleExplicitLod:
+    case SpvOpImageSampleDrefImplicitLod:
+    case SpvOpImageSampleDrefExplicitLod:
+    case SpvOpImageSampleProjImplicitLod:
+    case SpvOpImageSampleProjExplicitLod:
+    case SpvOpImageSampleProjDrefImplicitLod:
+    case SpvOpImageSampleProjDrefExplicitLod:
+    case SpvOpImageSparseSampleImplicitLod:
+    case SpvOpImageSparseSampleExplicitLod:
+    case SpvOpImageSparseSampleDrefImplicitLod:
+    case SpvOpImageSparseSampleDrefExplicitLod:
+      return true;
+    default:
+      return false;
+  }
+}
+
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode) {
   switch (opcode) {
     case SpvOpMemoryBarrier:
diff --git a/source/opcode.h b/source/opcode.h
index 0d8ec92..9aeba76 100644
--- a/source/opcode.h
+++ b/source/opcode.h
@@ -137,6 +137,9 @@
 // Returns true for opcodes that represents linear algebra instructions.
 bool spvOpcodeIsLinearAlgebra(SpvOp opcode);
 
+// Returns true for opcodes that represents an image sample instruction.
+bool spvOpcodeIsImageSample(SpvOp opcode);
+
 // Returns a vector containing the indices of the memory semantics <id>
 // operands for |opcode|.
 std::vector<uint32_t> spvOpcodeMemorySemanticsOperandIndices(SpvOp opcode);
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 8a71686..ddd6c68 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -36,6 +36,7 @@
           transformation_add_function_test.cpp
           transformation_add_global_undef_test.cpp
           transformation_add_global_variable_test.cpp
+          transformation_add_image_sample_unused_components_test.cpp
           transformation_add_local_variable_test.cpp
           transformation_add_no_contraction_decoration_test.cpp
           transformation_add_parameter_test.cpp
diff --git a/test/fuzz/transformation_add_image_sample_unused_components_test.cpp b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp
new file mode 100644
index 0000000..fc78f9f
--- /dev/null
+++ b/test/fuzz/transformation_add_image_sample_unused_components_test.cpp
@@ -0,0 +1,256 @@
+// Copyright (c) 2020 André Perez Maselco
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_add_image_sample_unused_components.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationAddImageSampleUnusedComponentsTest, IsApplicable) {
+  std::string shader = R"(
+               OpCapability Shader
+               OpCapability LiteralSampler
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %18 "main" %17
+               OpExecutionMode %18 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %18 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeImage %4 2D 0 0 0 1 Rgba32f
+          %9 = OpTypePointer Image %8
+         %10 = OpTypeSampledImage %8
+         %11 = OpTypeSampler
+         %12 = OpConstant %4 1
+         %13 = OpConstant %4 2
+         %14 = OpConstant %4 3
+         %15 = OpConstant %4 4
+         %16 = OpConstantSampler %11 None 0 Linear
+         %17 = OpVariable %9 Image
+         %18 = OpFunction %2 None %3
+         %19 = OpLabel
+         %20 = OpLoad %8 %17
+         %21 = OpSampledImage %10 %20 %16
+         %22 = OpCompositeConstruct %5 %12 %13
+         %23 = OpCompositeConstruct %6 %22 %14
+         %24 = OpCompositeConstruct %7 %23 %15
+         %25 = OpImageSampleImplicitLod %7 %21 %22
+         %26 = OpImageSampleExplicitLod %7 %21 %23 Lod %12
+         %27 = OpImageSampleExplicitLod %7 %21 %24 Lod %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  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);
+
+  // Tests applicable image instruction.
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
+  auto transformation =
+      TransformationAddImageSampleUnusedComponents(23, instruction_descriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instruction_descriptor =
+      MakeInstructionDescriptor(26, SpvOpImageSampleExplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(24, instruction_descriptor);
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests undefined image instructions.
+  instruction_descriptor =
+      MakeInstructionDescriptor(27, SpvOpImageSampleImplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(23, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instruction_descriptor =
+      MakeInstructionDescriptor(28, SpvOpImageSampleExplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(23, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non-image instructions.
+  instruction_descriptor = MakeInstructionDescriptor(19, SpvOpLabel, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(24, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  instruction_descriptor = MakeInstructionDescriptor(20, SpvOpLoad, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(24, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests coordinate operand being a vec4.
+  instruction_descriptor =
+      MakeInstructionDescriptor(27, SpvOpImageSampleExplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(22, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests undefined coordinate with unused operands.
+  instruction_descriptor =
+      MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(27, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests coordinate with unused operands being a non-OpCompositeConstruct
+  // instruction.
+  instruction_descriptor =
+      MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(21, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the first OpCompositeConstruct constituent not being the original
+  // coordinate.
+  instruction_descriptor =
+      MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(22, instruction_descriptor);
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationAddImageSampleUnusedComponentsTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+               OpCapability LiteralSampler
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %18 "main" %17
+               OpExecutionMode %18 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %18 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeImage %4 2D 0 0 0 1 Rgba32f
+          %9 = OpTypePointer Image %8
+         %10 = OpTypeSampledImage %8
+         %11 = OpTypeSampler
+         %12 = OpConstant %4 1
+         %13 = OpConstant %4 2
+         %14 = OpConstant %4 3
+         %15 = OpConstant %4 4
+         %16 = OpConstantSampler %11 None 0 Linear
+         %17 = OpVariable %9 Image
+         %18 = OpFunction %2 None %3
+         %19 = OpLabel
+         %20 = OpLoad %8 %17
+         %21 = OpSampledImage %10 %20 %16
+         %22 = OpCompositeConstruct %5 %12 %13
+         %23 = OpCompositeConstruct %6 %22 %14
+         %24 = OpCompositeConstruct %7 %23 %15
+         %25 = OpImageSampleImplicitLod %7 %21 %22
+         %26 = OpImageSampleExplicitLod %7 %21 %23 Lod %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  auto instruction_descriptor =
+      MakeInstructionDescriptor(25, SpvOpImageSampleImplicitLod, 0);
+  auto transformation =
+      TransformationAddImageSampleUnusedComponents(23, instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  instruction_descriptor =
+      MakeInstructionDescriptor(26, SpvOpImageSampleExplicitLod, 0);
+  transformation =
+      TransformationAddImageSampleUnusedComponents(24, instruction_descriptor);
+  transformation.Apply(context.get(), &transformation_context);
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+               OpCapability LiteralSampler
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %18 "main" %17
+               OpExecutionMode %18 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %18 "main"
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpTypeFloat 32
+          %5 = OpTypeVector %4 2
+          %6 = OpTypeVector %4 3
+          %7 = OpTypeVector %4 4
+          %8 = OpTypeImage %4 2D 0 0 0 1 Rgba32f
+          %9 = OpTypePointer Image %8
+         %10 = OpTypeSampledImage %8
+         %11 = OpTypeSampler
+         %12 = OpConstant %4 1
+         %13 = OpConstant %4 2
+         %14 = OpConstant %4 3
+         %15 = OpConstant %4 4
+         %16 = OpConstantSampler %11 None 0 Linear
+         %17 = OpVariable %9 Image
+         %18 = OpFunction %2 None %3
+         %19 = OpLabel
+         %20 = OpLoad %8 %17
+         %21 = OpSampledImage %10 %20 %16
+         %22 = OpCompositeConstruct %5 %12 %13
+         %23 = OpCompositeConstruct %6 %22 %14
+         %24 = OpCompositeConstruct %7 %23 %15
+         %25 = OpImageSampleImplicitLod %7 %21 %23
+         %26 = OpImageSampleExplicitLod %7 %21 %24 Lod %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools