spirv-fuzz: Add expand vector reduction transformation (#3869)

The following implementations are introduced:

- Transformation and fuzzer pass for expanding vector reduction.
- Unit tests to cover the instructions with different vector sizes.

Fixes #3768.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index a0e7ed8..63a6625 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -86,6 +86,7 @@
         fuzzer_pass_copy_objects.h
         fuzzer_pass_donate_modules.h
         fuzzer_pass_duplicate_regions_with_selections.h
+        fuzzer_pass_expand_vector_reductions.h
         fuzzer_pass_flatten_conditional_branches.h
         fuzzer_pass_inline_functions.h
         fuzzer_pass_invert_comparison_operators.h
@@ -179,6 +180,7 @@
         transformation_context.h
         transformation_duplicate_region_with_selection.h
         transformation_equation_instruction.h
+        transformation_expand_vector_reduction.h
         transformation_flatten_conditional_branch.h
         transformation_function_call.h
         transformation_inline_function.h
@@ -274,6 +276,7 @@
         fuzzer_pass_copy_objects.cpp
         fuzzer_pass_donate_modules.cpp
         fuzzer_pass_duplicate_regions_with_selections.cpp
+        fuzzer_pass_expand_vector_reductions.cpp
         fuzzer_pass_flatten_conditional_branches.cpp
         fuzzer_pass_inline_functions.cpp
         fuzzer_pass_invert_comparison_operators.cpp
@@ -365,6 +368,7 @@
         transformation_context.cpp
         transformation_duplicate_region_with_selection.cpp
         transformation_equation_instruction.cpp
+        transformation_expand_vector_reduction.cpp
         transformation_flatten_conditional_branch.cpp
         transformation_function_call.cpp
         transformation_inline_function.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index f326656..4361283 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -55,6 +55,7 @@
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
 #include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h"
+#include "source/fuzz/fuzzer_pass_expand_vector_reductions.h"
 #include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h"
 #include "source/fuzz/fuzzer_pass_inline_functions.h"
 #include "source/fuzz/fuzzer_pass_interchange_signedness_of_integer_operands.h"
@@ -259,6 +260,7 @@
                                                   donor_suppliers_);
     MaybeAddRepeatedPass<FuzzerPassDuplicateRegionsWithSelections>(
         &pass_instances);
+    MaybeAddRepeatedPass<FuzzerPassExpandVectorReductions>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassFlattenConditionalBranches>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassInlineFunctions>(&pass_instances);
     MaybeAddRepeatedPass<FuzzerPassInvertComparisonOperators>(&pass_instances);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index 3a27d6f..65904dc 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -81,6 +81,8 @@
 const std::pair<uint32_t, uint32_t> kChanceOfDonatingAdditionalModule = {5, 50};
 const std::pair<uint32_t, uint32_t> kChanceOfDuplicatingRegionWithSelection = {
     20, 50};
+const std::pair<uint32_t, uint32_t> kChanceOfExpandingVectorReduction = {20,
+                                                                         90};
 const std::pair<uint32_t, uint32_t> kChanceOfFlatteningConditionalBranch = {45,
                                                                             95};
 const std::pair<uint32_t, uint32_t> kChanceOfGoingDeeperToExtractComposite = {
@@ -266,6 +268,8 @@
       ChooseBetweenMinAndMax(kChanceOfDonatingAdditionalModule);
   chance_of_duplicating_region_with_selection_ =
       ChooseBetweenMinAndMax(kChanceOfDuplicatingRegionWithSelection);
+  chance_of_expanding_vector_reduction_ =
+      ChooseBetweenMinAndMax(kChanceOfExpandingVectorReduction);
   chance_of_flattening_conditional_branch_ =
       ChooseBetweenMinAndMax(kChanceOfFlatteningConditionalBranch);
   chance_of_going_deeper_to_extract_composite_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index b230af3..9193dfc 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -216,6 +216,9 @@
   uint32_t GetChanceOfDuplicatingRegionWithSelection() {
     return chance_of_duplicating_region_with_selection_;
   }
+  uint32_t GetChanceOfExpandingVectorReduction() {
+    return chance_of_expanding_vector_reduction_;
+  }
   uint32_t GetChanceOfFlatteningConditionalBranch() {
     return chance_of_flattening_conditional_branch_;
   }
@@ -461,6 +464,7 @@
   uint32_t chance_of_creating_int_synonyms_using_loops_;
   uint32_t chance_of_donating_additional_module_;
   uint32_t chance_of_duplicating_region_with_selection_;
+  uint32_t chance_of_expanding_vector_reduction_;
   uint32_t chance_of_flattening_conditional_branch_;
   uint32_t chance_of_going_deeper_to_extract_composite_;
   uint32_t chance_of_going_deeper_to_insert_in_composite_;
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
new file mode 100644
index 0000000..1416fe0
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.cpp
@@ -0,0 +1,67 @@
+// 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_expand_vector_reductions.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_expand_vector_reduction.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassExpandVectorReductions::FuzzerPassExpandVectorReductions(
+    opt::IRContext* ir_context, TransformationContext* transformation_context,
+    FuzzerContext* fuzzer_context,
+    protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassExpandVectorReductions::~FuzzerPassExpandVectorReductions() = default;
+
+void FuzzerPassExpandVectorReductions::Apply() {
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      for (auto& instruction : block) {
+        // Randomly decides whether the transformation will be applied.
+        if (!GetFuzzerContext()->ChoosePercentage(
+                GetFuzzerContext()->GetChanceOfExpandingVectorReduction())) {
+          continue;
+        }
+
+        // |instruction| must be OpAny or OpAll.
+        if (instruction.opcode() != SpvOpAny &&
+            instruction.opcode() != SpvOpAll) {
+          continue;
+        }
+
+        // It must be able to make a synonym of |instruction|.
+        if (!fuzzerutil::CanMakeSynonymOf(
+                GetIRContext(), *GetTransformationContext(), &instruction)) {
+          continue;
+        }
+
+        // Applies the expand vector reduction transformation.
+        ApplyTransformation(TransformationExpandVectorReduction(
+            instruction.result_id(),
+            GetFuzzerContext()->GetFreshIds(
+                TransformationExpandVectorReduction::GetRequiredFreshIdCount(
+                    GetIRContext(), &instruction))));
+      }
+    }
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_expand_vector_reductions.h b/source/fuzz/fuzzer_pass_expand_vector_reductions.h
new file mode 100644
index 0000000..ae3238b
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_expand_vector_reductions.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_EXPAND_VECTOR_REDUCTIONS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_EXPAND_VECTOR_REDUCTIONS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// This fuzzer pass adds synonyms for the OpAny and OpAll instructions. It
+// iterates over the module, checks if there are any OpAny or OpAll applicable
+// instructions and randomly applies the expand vector reduction transformation.
+class FuzzerPassExpandVectorReductions : public FuzzerPass {
+ public:
+  FuzzerPassExpandVectorReductions(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassExpandVectorReductions();
+
+  void Apply() override;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_EXPAND_VECTOR_REDUCTIONS_H_
diff --git a/source/fuzz/pass_management/repeated_pass_instances.h b/source/fuzz/pass_management/repeated_pass_instances.h
index 5f479fb..80ac087 100644
--- a/source/fuzz/pass_management/repeated_pass_instances.h
+++ b/source/fuzz/pass_management/repeated_pass_instances.h
@@ -43,6 +43,7 @@
 #include "source/fuzz/fuzzer_pass_copy_objects.h"
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
 #include "source/fuzz/fuzzer_pass_duplicate_regions_with_selections.h"
+#include "source/fuzz/fuzzer_pass_expand_vector_reductions.h"
 #include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h"
 #include "source/fuzz/fuzzer_pass_inline_functions.h"
 #include "source/fuzz/fuzzer_pass_invert_comparison_operators.h"
@@ -137,6 +138,7 @@
   REPEATED_PASS_INSTANCE(CopyObjects);
   REPEATED_PASS_INSTANCE(DonateModules);
   REPEATED_PASS_INSTANCE(DuplicateRegionsWithSelections);
+  REPEATED_PASS_INSTANCE(ExpandVectorReductions);
   REPEATED_PASS_INSTANCE(FlattenConditionalBranches);
   REPEATED_PASS_INSTANCE(InlineFunctions);
   REPEATED_PASS_INSTANCE(InvertComparisonOperators);
diff --git a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
index 7121c33..a933848 100644
--- a/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
+++ b/source/fuzz/pass_management/repeated_pass_recommender_standard.cpp
@@ -206,6 +206,10 @@
     // - Parts of duplicated regions can be outlined
     return RandomOrderAndNonNull({pass_instances_->GetOutlineFunctions()});
   }
+  if (&pass == pass_instances_->GetExpandVectorReductions()) {
+    // - Adding OpAny and OpAll synonyms creates opportunities to apply synonyms
+    return RandomOrderAndNonNull({pass_instances_->GetApplyIdSynonyms()});
+  }
   if (&pass == pass_instances_->GetFlattenConditionalBranches()) {
     // - Parts of flattened selections can be outlined
     // - The flattening transformation introduces constants and irrelevant ids
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 20116c7..770a2dd 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -556,6 +556,7 @@
     TransformationReplaceBranchFromDeadBlockWithExit replace_branch_from_dead_block_with_exit = 82;
     TransformationWrapEarlyTerminatorInFunction wrap_early_terminator_in_function = 83;
     TransformationMergeFunctionReturns merge_function_returns = 84;
+    TransformationExpandVectorReduction expand_vector_reduction = 85;
     // Add additional option using the next available number.
   }
 }
@@ -1417,6 +1418,21 @@
 
 }
 
+message TransformationExpandVectorReduction {
+
+  // A transformation that adds synonyms for OpAny and OpAll instructions by
+  // evaluating each vector component with the corresponding logical operation.
+  // There is a SPIR-V code example in the header file of the transformation
+  // class that can help understand the transformation.
+
+  // The OpAny or OpAll instruction result id.
+  uint32 instruction_result_id = 1;
+
+  // The fresh ids required to apply the transformation.
+  repeated uint32 fresh_ids = 2;
+
+}
+
 message TransformationFlattenConditionalBranch {
 
   // A transformation that takes a selection construct with a header
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index c8bf879..ebbc393 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -57,6 +57,7 @@
 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
 #include "source/fuzz/transformation_duplicate_region_with_selection.h"
 #include "source/fuzz/transformation_equation_instruction.h"
+#include "source/fuzz/transformation_expand_vector_reduction.h"
 #include "source/fuzz/transformation_flatten_conditional_branch.h"
 #include "source/fuzz/transformation_function_call.h"
 #include "source/fuzz/transformation_inline_function.h"
@@ -225,6 +226,9 @@
     case protobufs::Transformation::TransformationCase::kEquationInstruction:
       return MakeUnique<TransformationEquationInstruction>(
           message.equation_instruction());
+    case protobufs::Transformation::TransformationCase::kExpandVectorReduction:
+      return MakeUnique<TransformationExpandVectorReduction>(
+          message.expand_vector_reduction());
     case protobufs::Transformation::TransformationCase::
         kFlattenConditionalBranch:
       return MakeUnique<TransformationFlattenConditionalBranch>(
diff --git a/source/fuzz/transformation_expand_vector_reduction.cpp b/source/fuzz/transformation_expand_vector_reduction.cpp
new file mode 100644
index 0000000..640aea2
--- /dev/null
+++ b/source/fuzz/transformation_expand_vector_reduction.cpp
@@ -0,0 +1,170 @@
+// 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_expand_vector_reduction.h"
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationExpandVectorReduction::TransformationExpandVectorReduction(
+    const spvtools::fuzz::protobufs::TransformationExpandVectorReduction&
+        message)
+    : message_(message) {}
+
+TransformationExpandVectorReduction::TransformationExpandVectorReduction(
+    const uint32_t instruction_result_id,
+    const std::vector<uint32_t>& fresh_ids) {
+  message_.set_instruction_result_id(instruction_result_id);
+  *message_.mutable_fresh_ids() =
+      google::protobuf::RepeatedField<google::protobuf::uint32>(
+          fresh_ids.begin(), fresh_ids.end());
+}
+
+bool TransformationExpandVectorReduction::IsApplicable(
+    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+  auto* instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+
+  // |instruction| must be defined.
+  if (!instruction) {
+    return false;
+  }
+
+  // |instruction| must be OpAny or OpAll.
+  if (instruction->opcode() != SpvOpAny && instruction->opcode() != SpvOpAll) {
+    return false;
+  }
+
+  // |message_.fresh_ids.size| must have the exact number of fresh ids required
+  // to apply the transformation.
+  if (static_cast<uint32_t>(message_.fresh_ids().size()) !=
+      GetRequiredFreshIdCount(ir_context, instruction)) {
+    return false;
+  }
+
+  std::set<uint32_t> ids_used_by_this_transformation;
+  for (uint32_t fresh_id : message_.fresh_ids()) {
+    // All ids in |message_.fresh_ids| must be fresh.
+    if (!fuzzerutil::IsFreshId(ir_context, fresh_id)) {
+      return false;
+    }
+
+    // All fresh ids need to be distinct.
+    if (!CheckIdIsFreshAndNotUsedByThisTransformation(
+            fresh_id, ir_context, &ids_used_by_this_transformation)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+void TransformationExpandVectorReduction::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* transformation_context) const {
+  auto* instruction =
+      ir_context->get_def_use_mgr()->GetDef(message_.instruction_result_id());
+  auto* vector = ir_context->get_def_use_mgr()->GetDef(
+      instruction->GetSingleWordInOperand(0));
+  uint32_t vector_component_count = ir_context->get_type_mgr()
+                                        ->GetType(vector->type_id())
+                                        ->AsVector()
+                                        ->element_count();
+
+  // Fresh id iterator.
+  auto fresh_id = message_.fresh_ids().begin();
+
+  // |vector_components| are the ids of the extracted components from |vector|.
+  std::vector<uint32_t> vector_components;
+
+  for (uint32_t i = 0; i < vector_component_count; i++) {
+    // Extracts the i-th |vector| component.
+    auto vector_component = opt::Instruction(
+        ir_context, SpvOpCompositeExtract, instruction->type_id(), *fresh_id++,
+        {{SPV_OPERAND_TYPE_ID, {vector->result_id()}},
+         {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}});
+    instruction->InsertBefore(MakeUnique<opt::Instruction>(vector_component));
+    fuzzerutil::UpdateModuleIdBound(ir_context, vector_component.result_id());
+    vector_components.push_back(vector_component.result_id());
+  }
+
+  // The first two |vector| components are used in the first logical operation.
+  auto logical_instruction = opt::Instruction(
+      ir_context,
+      instruction->opcode() == SpvOpAny ? SpvOpLogicalOr : SpvOpLogicalAnd,
+      instruction->type_id(), *fresh_id++,
+      {{SPV_OPERAND_TYPE_ID, {vector_components[0]}},
+       {SPV_OPERAND_TYPE_ID, {vector_components[1]}}});
+  instruction->InsertBefore(MakeUnique<opt::Instruction>(logical_instruction));
+  fuzzerutil::UpdateModuleIdBound(ir_context, logical_instruction.result_id());
+
+  // Evaluates the remaining components.
+  for (uint32_t i = 2; i < vector_components.size(); i++) {
+    logical_instruction = opt::Instruction(
+        ir_context, logical_instruction.opcode(), instruction->type_id(),
+        *fresh_id++,
+        {{SPV_OPERAND_TYPE_ID, {vector_components[i]}},
+         {SPV_OPERAND_TYPE_ID, {logical_instruction.result_id()}}});
+    instruction->InsertBefore(
+        MakeUnique<opt::Instruction>(logical_instruction));
+    fuzzerutil::UpdateModuleIdBound(ir_context,
+                                    logical_instruction.result_id());
+  }
+
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+
+  // If it's possible to make a synonym of |instruction|, then add the fact that
+  // the last |logical_instruction| is a synonym of |instruction|.
+  if (fuzzerutil::CanMakeSynonymOf(ir_context, *transformation_context,
+                                   instruction)) {
+    transformation_context->GetFactManager()->AddFactDataSynonym(
+        MakeDataDescriptor(logical_instruction.result_id(), {}),
+        MakeDataDescriptor(instruction->result_id(), {}));
+  }
+}
+
+protobufs::Transformation TransformationExpandVectorReduction::ToMessage()
+    const {
+  protobufs::Transformation result;
+  *result.mutable_expand_vector_reduction() = message_;
+  return result;
+}
+
+uint32_t TransformationExpandVectorReduction::GetRequiredFreshIdCount(
+    opt::IRContext* ir_context, opt::Instruction* instruction) {
+  // For each vector component, 1 OpCompositeExtract and 1 OpLogical* (except
+  // for the first component) instructions will be inserted.
+  return 2 * ir_context->get_type_mgr()
+                 ->GetType(ir_context->get_def_use_mgr()
+                               ->GetDef(instruction->GetSingleWordInOperand(0))
+                               ->type_id())
+                 ->AsVector()
+                 ->element_count() -
+         1;
+}
+
+std::unordered_set<uint32_t> TransformationExpandVectorReduction::GetFreshIds()
+    const {
+  std::unordered_set<uint32_t> result;
+  for (auto id : message_.fresh_ids()) {
+    result.insert(id);
+  }
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_expand_vector_reduction.h b/source/fuzz/transformation_expand_vector_reduction.h
new file mode 100644
index 0000000..e4cc953
--- /dev/null
+++ b/source/fuzz/transformation_expand_vector_reduction.h
@@ -0,0 +1,105 @@
+// 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_EXPAND_VECTOR_REDUCTION_H_
+#define SOURCE_FUZZ_TRANSFORMATION_EXPAND_VECTOR_REDUCTION_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 {
+
+// clang-format off
+// SPIR-V code to help understand the transformation.
+//
+// -------------------------------------------------------------------------------
+// |           Reference shader           |            Variant shader            |
+// -------------------------------------------------------------------------------
+// |       OpCapability Shader            |       OpCapability Shader            |
+// |  %1 = OpExtInstImport "GLSL.std.450" |  %1 = OpExtInstImport "GLSL.std.450" |
+// |       OpMemoryModel Logical GLSL450  |       OpMemoryModel Logical GLSL450  |
+// |       OpEntryPoint Vertex %9 "main"  |       OpEntryPoint Vertex %9 "main"  |
+// |                                      |                                      |
+// | ; Types                              | ; Types                              |
+// |  %2 = OpTypeBool                     |  %2 = OpTypeBool                     |
+// |  %3 = OpTypeVector %2 2              |  %3 = OpTypeVector %2 2              |
+// |  %4 = OpTypeVoid                     |  %4 = OpTypeVoid                     |
+// |  %5 = OpTypeFunction %4              |  %5 = OpTypeFunction %4              |
+// |                                      |                                      |
+// | ; Constants                          | ; Constants                          |
+// |  %6 = OpConstantTrue %2              |  %6 = OpConstantTrue %2              |
+// |  %7 = OpConstantFalse %2             |  %7 = OpConstantFalse %2             |
+// |  %8 = OpConstantComposite %3 %6 %7   |  %8 = OpConstantComposite %3 %6 %7   |
+// |                                      |                                      |
+// | ; main function                      | ; main function                      |
+// |  %9 = OpFunction %4 None %5          |  %9 = OpFunction %4 None %5          |
+// | %10 = OpLabel                        | %10 = OpLabel                        |
+// | %11 = OpAny %2 %8                    |                                      |
+// | %12 = OpAll %2 %8                    | ; Add OpAny synonym                  |
+// |       OpReturn                       | %13 = OpCompositeExtract %2 %8 0     |
+// |       OpFunctionEnd                  | %14 = OpCompositeExtract %2 %8 1     |
+// |                                      | %15 = OpLogicalOr %2 %13 %14         |
+// |                                      | %11 = OpAny %2 %8                    |
+// |                                      |                                      |
+// |                                      | ; Add OpAll synonym                  |
+// |                                      | %16 = OpCompositeExtract %2 %8 0     |
+// |                                      | %17 = OpCompositeExtract %2 %8 1     |
+// |                                      | %18 = OpLogicalAnd %2 %16 %17        |
+// |                                      | %12 = OpAll %2 %8                    |
+// |                                      |       OpReturn                       |
+// |                                      |       OpFunctionEnd                  |
+// -------------------------------------------------------------------------------
+//
+// %11 and %15 are synonymous
+// %12 and %18 are synonymous
+// clang-format on
+class TransformationExpandVectorReduction : public Transformation {
+ public:
+  explicit TransformationExpandVectorReduction(
+      const protobufs::TransformationExpandVectorReduction& message);
+
+  TransformationExpandVectorReduction(const uint32_t instruction_result_id,
+                                      const std::vector<uint32_t>& fresh_ids);
+
+  // - |message_.instruction_result_id| must be OpAny or OpAll.
+  // - |message_.fresh_ids| must be fresh ids needed to apply the
+  //   transformation.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Adds synonyms for OpAny and OpAll instructions by evaluating each vector
+  // component with the corresponding logical operation.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  std::unordered_set<uint32_t> GetFreshIds() const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+  // Returns the number of fresh ids required to apply the transformation.
+  static uint32_t GetRequiredFreshIdCount(opt::IRContext* ir_context,
+                                          opt::Instruction* instruction);
+
+ private:
+  protobufs::TransformationExpandVectorReduction message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_EXPAND_VECTOR_REDUCTION_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 866a924..2e93293 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -73,6 +73,7 @@
           transformation_compute_data_synonym_fact_closure_test.cpp
           transformation_duplicate_region_with_selection_test.cpp
           transformation_equation_instruction_test.cpp
+          transformation_expand_vector_reduction_test.cpp
           transformation_flatten_conditional_branch_test.cpp
           transformation_function_call_test.cpp
           transformation_inline_function_test.cpp
diff --git a/test/fuzz/transformation_expand_vector_reduction_test.cpp b/test/fuzz/transformation_expand_vector_reduction_test.cpp
new file mode 100644
index 0000000..ae5c4af
--- /dev/null
+++ b/test/fuzz/transformation_expand_vector_reduction_test.cpp
@@ -0,0 +1,284 @@
+// 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_expand_vector_reduction.h"
+
+#include "gtest/gtest.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationExpandVectorReductionTest, IsApplicable) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %9 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVoid
+          %5 = OpTypeFunction %4
+
+          ; Constants
+          %6 = OpConstantTrue %2
+          %7 = OpConstantFalse %2
+          %8 = OpConstantComposite %3 %6 %7
+
+          ; main function
+          %9 = OpFunction %4 None %5
+         %10 = OpLabel
+         %11 = OpAny %2 %8
+         %12 = OpAll %2 %8
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Tests undefined instruction.
+  auto transformation = TransformationExpandVectorReduction(13, {14, 15, 16});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non OpAny or OpAll instruction.
+  transformation = TransformationExpandVectorReduction(10, {13, 14, 15});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests the number of fresh ids being different than the necessary.
+  transformation = TransformationExpandVectorReduction(11, {13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationExpandVectorReduction(12, {13, 14, 15, 16});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests non-fresh ids.
+  transformation = TransformationExpandVectorReduction(11, {12, 13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests duplicated fresh ids.
+  transformation = TransformationExpandVectorReduction(11, {13, 13, 14});
+  ASSERT_FALSE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  // Tests applicable transformations.
+  transformation = TransformationExpandVectorReduction(11, {13, 14, 15});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+
+  transformation = TransformationExpandVectorReduction(12, {13, 14, 15});
+  ASSERT_TRUE(
+      transformation.IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationExpandVectorReductionTest, Apply) {
+  std::string reference_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %13 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVector %2 3
+          %5 = OpTypeVector %2 4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+
+          ; Constants
+          %8 = OpConstantTrue %2
+          %9 = OpConstantFalse %2
+         %10 = OpConstantComposite %3 %8 %9
+         %11 = OpConstantComposite %4 %8 %9 %8
+         %12 = OpConstantComposite %5 %8 %9 %8 %9
+
+         ; main function
+         %13 = OpFunction %6 None %7
+         %14 = OpLabel
+
+         ; OpAny for 2-dimensional vector
+         %15 = OpAny %2 %10
+
+         ; OpAny for 3-dimensional vector
+         %16 = OpAny %2 %11
+
+         ; OpAny for 4-dimensional vector
+         %17 = OpAny %2 %12
+
+         ; OpAll for 2-dimensional vector
+         %18 = OpAll %2 %10
+
+         ; OpAll for 3-dimensional vector
+         %19 = OpAll %2 %11
+
+         ; OpAll for 4-dimensional vector
+         %20 = OpAll %2 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context =
+      BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(
+      MakeUnique<FactManager>(context.get()), validator_options);
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+
+  // Adds OpAny synonym for 2-dimensional vector.
+  auto transformation = TransformationExpandVectorReduction(15, {21, 22, 23});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(23, {}), MakeDataDescriptor(15, {})));
+
+  // Adds OpAny synonym for 3-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(16, {24, 25, 26, 27, 28});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(28, {}), MakeDataDescriptor(16, {})));
+
+  // Adds OpAny synonym for 4-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(17, {29, 30, 31, 32, 33, 34, 35});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(35, {}), MakeDataDescriptor(17, {})));
+
+  // Adds OpAll synonym for 2-dimensional vector.
+  transformation = TransformationExpandVectorReduction(18, {36, 37, 38});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(38, {}), MakeDataDescriptor(18, {})));
+
+  // Adds OpAll synonym for 3-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(19, {39, 40, 41, 42, 43});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(43, {}), MakeDataDescriptor(19, {})));
+
+  // Adds OpAll synonym for 4-dimensional vector.
+  transformation =
+      TransformationExpandVectorReduction(20, {44, 45, 46, 47, 48, 49, 50});
+  ApplyAndCheckFreshIds(transformation, context.get(), &transformation_context);
+  ASSERT_TRUE(transformation_context.GetFactManager()->IsSynonymous(
+      MakeDataDescriptor(50, {}), MakeDataDescriptor(20, {})));
+
+  std::string variant_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %13 "main"
+
+          ; Types
+          %2 = OpTypeBool
+          %3 = OpTypeVector %2 2
+          %4 = OpTypeVector %2 3
+          %5 = OpTypeVector %2 4
+          %6 = OpTypeVoid
+          %7 = OpTypeFunction %6
+
+          ; Constants
+          %8 = OpConstantTrue %2
+          %9 = OpConstantFalse %2
+         %10 = OpConstantComposite %3 %8 %9
+         %11 = OpConstantComposite %4 %8 %9 %8
+         %12 = OpConstantComposite %5 %8 %9 %8 %9
+
+         ; main function
+         %13 = OpFunction %6 None %7
+         %14 = OpLabel
+
+         ; Add OpAny synonym for 2-dimensional vector
+         %21 = OpCompositeExtract %2 %10 0
+         %22 = OpCompositeExtract %2 %10 1
+         %23 = OpLogicalOr %2 %21 %22
+         %15 = OpAny %2 %10
+
+         ; Add OpAny synonym for 3-dimensional vector
+         %24 = OpCompositeExtract %2 %11 0
+         %25 = OpCompositeExtract %2 %11 1
+         %26 = OpCompositeExtract %2 %11 2
+         %27 = OpLogicalOr %2 %24 %25
+         %28 = OpLogicalOr %2 %26 %27
+         %16 = OpAny %2 %11
+
+         ; Add OpAny synonym for 4-dimensional vector
+         %29 = OpCompositeExtract %2 %12 0
+         %30 = OpCompositeExtract %2 %12 1
+         %31 = OpCompositeExtract %2 %12 2
+         %32 = OpCompositeExtract %2 %12 3
+         %33 = OpLogicalOr %2 %29 %30
+         %34 = OpLogicalOr %2 %31 %33
+         %35 = OpLogicalOr %2 %32 %34
+         %17 = OpAny %2 %12
+
+         ; Add OpAll synonym for 2-dimensional vector
+         %36 = OpCompositeExtract %2 %10 0
+         %37 = OpCompositeExtract %2 %10 1
+         %38 = OpLogicalAnd %2 %36 %37
+         %18 = OpAll %2 %10
+
+         ; Add OpAll synonym for 3-dimensional vector
+         %39 = OpCompositeExtract %2 %11 0
+         %40 = OpCompositeExtract %2 %11 1
+         %41 = OpCompositeExtract %2 %11 2
+         %42 = OpLogicalAnd %2 %39 %40
+         %43 = OpLogicalAnd %2 %41 %42
+         %19 = OpAll %2 %11
+
+         ; Add OpAll synonym for 4-dimensional vector
+         %44 = OpCompositeExtract %2 %12 0
+         %45 = OpCompositeExtract %2 %12 1
+         %46 = OpCompositeExtract %2 %12 2
+         %47 = OpCompositeExtract %2 %12 3
+         %48 = OpLogicalAnd %2 %44 %45
+         %49 = OpLogicalAnd %2 %46 %48
+         %50 = OpLogicalAnd %2 %47 %49
+         %20 = OpAll %2 %12
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options,
+                                               kConsoleMessageConsumer));
+  ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools