spirv-fuzz: Replace id in OpPhi coming from a dead predecessor (#3744)

This transformation takes the id of an OpPhi instruction, of a dead
predecessor of the block containing it and a replacement id of
available to use and of the same type as the OpPhi, and changes
the id in the OpPhi corresponding to the given predecessor.

For example, %id = OpPhi %type %v1 %p1 %v2 %p2
becomes %id = OpPhi %type %v3 %p1 %v2 %p2
if the transformation is given %id, %p1 and %v3, %p1 is a dead block,
%v3 is type type and it is available to use at the end of %p1.

The fuzzer pass randomly decides to apply the transformation to OpPhi
instructions for which at least one of the predecessors is dead

Fixes #3726.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 570cc7a..288ea25 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -94,6 +94,7 @@
         fuzzer_pass_replace_irrelevant_ids.h
         fuzzer_pass_replace_linear_algebra_instructions.h
         fuzzer_pass_replace_loads_stores_with_copy_memories.h
+        fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
         fuzzer_pass_replace_parameter_with_global.h
         fuzzer_pass_replace_params_with_struct.h
         fuzzer_pass_split_blocks.h
@@ -172,6 +173,7 @@
         transformation_replace_irrelevant_id.h
         transformation_replace_linear_algebra_instruction.h
         transformation_replace_load_store_with_copy_memory.h
+        transformation_replace_opphi_id_from_dead_predecessor.h
         transformation_replace_parameter_with_global.h
         transformation_replace_params_with_struct.h
         transformation_set_function_control.h
@@ -251,6 +253,7 @@
         fuzzer_pass_replace_irrelevant_ids.cpp
         fuzzer_pass_replace_linear_algebra_instructions.cpp
         fuzzer_pass_replace_loads_stores_with_copy_memories.cpp
+        fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
         fuzzer_pass_replace_parameter_with_global.cpp
         fuzzer_pass_replace_params_with_struct.cpp
         fuzzer_pass_split_blocks.cpp
@@ -328,6 +331,7 @@
         transformation_replace_irrelevant_id.cpp
         transformation_replace_linear_algebra_instruction.cpp
         transformation_replace_load_store_with_copy_memory.cpp
+        transformation_replace_opphi_id_from_dead_predecessor.cpp
         transformation_replace_parameter_with_global.cpp
         transformation_replace_params_with_struct.cpp
         transformation_set_function_control.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 6107f45..3656e92 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -70,6 +70,7 @@
 #include "source/fuzz/fuzzer_pass_replace_irrelevant_ids.h"
 #include "source/fuzz/fuzzer_pass_replace_linear_algebra_instructions.h"
 #include "source/fuzz/fuzzer_pass_replace_loads_stores_with_copy_memories.h"
+#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
 #include "source/fuzz/fuzzer_pass_replace_parameter_with_global.h"
 #include "source/fuzz/fuzzer_pass_replace_params_with_struct.h"
 #include "source/fuzz/fuzzer_pass_split_blocks.h"
@@ -379,6 +380,9 @@
   MaybeAddPass<FuzzerPassPermutePhiOperands>(
       &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
       transformation_sequence_out);
+  MaybeAddPass<FuzzerPassReplaceOpPhiIdsFromDeadPredecessors>(
+      &final_passes, ir_context.get(), &transformation_context, &fuzzer_context,
+      transformation_sequence_out);
   MaybeAddPass<FuzzerPassReplaceIrrelevantIds>(
       &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 1dfa717..153b9ef 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -103,6 +103,8 @@
     kChanceOfReplacingLinearAlgebraInstructions = {10, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingLoadStoreWithCopyMemory =
     {20, 90};
+const std::pair<uint32_t, uint32_t>
+    kChanceOfReplacingOpPhiIdFromDeadPredecessor = {20, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithGlobals = {
     30, 70};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingParametersWithStruct = {
@@ -267,6 +269,8 @@
       ChooseBetweenMinAndMax(kChanceOfReplacingLinearAlgebraInstructions);
   chance_of_replacing_load_store_with_copy_memory_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingLoadStoreWithCopyMemory);
+  chance_of_replacing_opphi_id_from_dead_predecessor_ =
+      ChooseBetweenMinAndMax(kChanceOfReplacingOpPhiIdFromDeadPredecessor);
   chance_of_replacing_parameters_with_globals_ =
       ChooseBetweenMinAndMax(kChanceOfReplacingParametersWithGlobals);
   chance_of_replacing_parameters_with_struct_ =
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 14ada03..594cb90 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -261,6 +261,9 @@
   uint32_t GetChanceOfReplacingLoadStoreWithCopyMemory() {
     return chance_of_replacing_load_store_with_copy_memory_;
   }
+  uint32_t GetChanceOfReplacingOpPhiIdFromDeadPredecessor() {
+    return chance_of_replacing_opphi_id_from_dead_predecessor_;
+  }
   uint32_t GetChanceOfReplacingParametersWithGlobals() {
     return chance_of_replacing_parameters_with_globals_;
   }
@@ -417,6 +420,7 @@
   uint32_t chance_of_replacing_irrelevant_id_;
   uint32_t chance_of_replacing_linear_algebra_instructions_;
   uint32_t chance_of_replacing_load_store_with_copy_memory_;
+  uint32_t chance_of_replacing_opphi_id_from_dead_predecessor_;
   uint32_t chance_of_replacing_parameters_with_globals_;
   uint32_t chance_of_replacing_parameters_with_struct_;
   uint32_t chance_of_splitting_block_;
diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
new file mode 100644
index 0000000..7d89e38
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.cpp
@@ -0,0 +1,117 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h"
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::
+    FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(
+        opt::IRContext* ir_context,
+        TransformationContext* transformation_context,
+        FuzzerContext* fuzzer_context,
+        protobufs::TransformationSequence* transformations)
+    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
+                 transformations) {}
+
+FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::
+    ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors() = default;
+
+void FuzzerPassReplaceOpPhiIdsFromDeadPredecessors::Apply() {
+  // Keep a vector of the transformations to apply.
+  std::vector<TransformationReplaceOpPhiIdFromDeadPredecessor> transformations;
+
+  // Loop through the blocks in the module.
+  for (auto& function : *GetIRContext()->module()) {
+    for (auto& block : function) {
+      // Only consider dead blocks.
+      if (!GetTransformationContext()->GetFactManager()->BlockIsDead(
+              block.id())) {
+        continue;
+      }
+
+      // Find all the uses of the label id of the block inside OpPhi
+      // instructions.
+      GetIRContext()->get_def_use_mgr()->ForEachUse(
+          block.id(), [this, &function, &block, &transformations](
+                          opt::Instruction* instruction, uint32_t) {
+            // Only consider OpPhi instructions.
+            if (instruction->opcode() != SpvOpPhi) {
+              return;
+            }
+
+            // Randomly decide whether to consider this use.
+            if (!GetFuzzerContext()->ChoosePercentage(
+                    GetFuzzerContext()
+                        ->GetChanceOfReplacingOpPhiIdFromDeadPredecessor())) {
+              return;
+            }
+
+            // Get the current id corresponding to the predecessor.
+            uint32_t current_id = 0;
+            for (uint32_t i = 1; i < instruction->NumInOperands(); i += 2) {
+              if (instruction->GetSingleWordInOperand(i) == block.id()) {
+                // The corresponding id is at the index of the block - 1.
+                current_id = instruction->GetSingleWordInOperand(i - 1);
+                break;
+              }
+            }
+            assert(current_id != 0 &&
+                   "The predecessor - and corresponding id - should always be "
+                   "found.");
+
+            uint32_t type_id = instruction->type_id();
+
+            // Find all the suitable instructions to replace the id.
+            const auto& candidates = FindAvailableInstructions(
+                &function, &block, block.end(),
+                [type_id, current_id](opt::IRContext* /* unused */,
+                                      opt::Instruction* candidate) -> bool {
+
+                  // Only consider instructions with a result id different from
+                  // the currently-used one, and with the right type.
+                  return candidate->HasResultId() &&
+                         candidate->type_id() == type_id &&
+                         candidate->result_id() != current_id;
+                });
+
+            // If there is no possible replacement, we cannot apply any
+            // transformation.
+            if (candidates.empty()) {
+              return;
+            }
+
+            // Choose one of the candidates.
+            uint32_t replacement_id =
+                candidates[GetFuzzerContext()->RandomIndex(candidates)]
+                    ->result_id();
+
+            // Add a new transformation to the list of transformations to apply.
+            transformations.emplace_back(
+                TransformationReplaceOpPhiIdFromDeadPredecessor(
+                    instruction->result_id(), block.id(), replacement_id));
+          });
+    }
+  }
+
+  // Apply all the transformations.
+  for (const auto& transformation : transformations) {
+    ApplyTransformation(transformation);
+  }
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
new file mode 100644
index 0000000..972c5f9
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_replace_opphi_ids_from_dead_predecessors.h
@@ -0,0 +1,39 @@
+// 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_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Replaces id operands in OpPhi instructions with other available ids of the
+// right type, where the corresponding predecessor is dead.
+class FuzzerPassReplaceOpPhiIdsFromDeadPredecessors : public FuzzerPass {
+ public:
+  FuzzerPassReplaceOpPhiIdsFromDeadPredecessors(
+      opt::IRContext* ir_context, TransformationContext* transformation_context,
+      FuzzerContext* fuzzer_context,
+      protobufs::TransformationSequence* transformations);
+
+  ~FuzzerPassReplaceOpPhiIdsFromDeadPredecessors();
+
+  void Apply() override;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_FUZZER_PASS_REPLACE_OPPHI_IDS_FROM_DEAD_PREDECESSORS_H_
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 0ac485f..0cc7a78 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -417,6 +417,7 @@
     TransformationAddOpPhiSynonym add_opphi_synonym = 70;
     TransformationMutatePointer mutate_pointer = 71;
     TransformationReplaceIrrelevantId replace_irrelevant_id = 72;
+    TransformationReplaceOpPhiIdFromDeadPredecessor replace_opphi_id_from_dead_predecessor = 73;
     // Add additional option using the next available number.
   }
 }
@@ -1543,6 +1544,26 @@
   InstructionDescriptor store_instruction_descriptor = 2;
 }
 
+message TransformationReplaceOpPhiIdFromDeadPredecessor {
+
+  // Replaces one of the ids used by an OpPhi instruction, when
+  // the corresponding predecessor is dead, with any available id
+  // of the correct type.
+
+  // The result id of the OpPhi instruction.
+  uint32 opphi_id = 1;
+
+  // The label id of one of the predecessors of the block containing
+  // the OpPhi instruction, corresponding to the id that we want to
+  // replace.
+  uint32 pred_label_id = 2;
+
+  // The id that, after the transformation, will be associated with
+  // the given predecessor.
+  uint32 replacement_id = 3;
+
+}
+
 message TransformationReplaceParamsWithStruct {
 
   // Replaces parameters of the function with a struct containing
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index e6baa15..2360901 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -77,6 +77,7 @@
 #include "source/fuzz/transformation_replace_irrelevant_id.h"
 #include "source/fuzz/transformation_replace_linear_algebra_instruction.h"
 #include "source/fuzz/transformation_replace_load_store_with_copy_memory.h"
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
 #include "source/fuzz/transformation_replace_parameter_with_global.h"
 #include "source/fuzz/transformation_replace_params_with_struct.h"
 #include "source/fuzz/transformation_set_function_control.h"
@@ -283,6 +284,10 @@
         kReplaceParamsWithStruct:
       return MakeUnique<TransformationReplaceParamsWithStruct>(
           message.replace_params_with_struct());
+    case protobufs::Transformation::TransformationCase::
+        kReplaceOpphiIdFromDeadPredecessor:
+      return MakeUnique<TransformationReplaceOpPhiIdFromDeadPredecessor>(
+          message.replace_opphi_id_from_dead_predecessor());
     case protobufs::Transformation::TransformationCase::kSetFunctionControl:
       return MakeUnique<TransformationSetFunctionControl>(
           message.set_function_control());
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
new file mode 100644
index 0000000..d5d324b
--- /dev/null
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.cpp
@@ -0,0 +1,110 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+
+#include "source/fuzz/fuzzer_util.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationReplaceOpPhiIdFromDeadPredecessor::
+    TransformationReplaceOpPhiIdFromDeadPredecessor(
+        const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
+            message)
+    : message_(message) {}
+
+TransformationReplaceOpPhiIdFromDeadPredecessor::
+    TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
+                                                    uint32_t pred_label_id,
+                                                    uint32_t replacement_id) {
+  message_.set_opphi_id(opphi_id);
+  message_.set_pred_label_id(pred_label_id);
+  message_.set_replacement_id(replacement_id);
+}
+
+bool TransformationReplaceOpPhiIdFromDeadPredecessor::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  // |opphi_id| must be the id of an OpPhi instruction.
+  auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id());
+  if (!opphi_def || opphi_def->opcode() != SpvOpPhi) {
+    return false;
+  }
+
+  // |pred_label_id| must be the label id of a dead block.
+  auto pred_block = ir_context->get_instr_block(message_.pred_label_id());
+  if (!pred_block || pred_block->id() != message_.pred_label_id() ||
+      !transformation_context.GetFactManager()->BlockIsDead(pred_block->id())) {
+    return false;
+  }
+
+  // |pred_label_id| must be one of the predecessors of the block containing the
+  // OpPhi instruction.
+  bool found = false;
+  for (auto pred :
+       ir_context->cfg()->preds(ir_context->get_instr_block(opphi_def)->id())) {
+    if (pred == message_.pred_label_id()) {
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    return false;
+  }
+
+  // |replacement_id| must have the same type id as the OpPhi instruction.
+  auto replacement_def =
+      ir_context->get_def_use_mgr()->GetDef(message_.replacement_id());
+
+  if (!replacement_def || replacement_def->type_id() != opphi_def->type_id()) {
+    return false;
+  }
+
+  // The replacement id must be available at the end of the predecessor.
+  return fuzzerutil::IdIsAvailableBeforeInstruction(
+      ir_context, pred_block->terminator(), replacement_def->result_id());
+}
+
+void TransformationReplaceOpPhiIdFromDeadPredecessor::Apply(
+    opt::IRContext* ir_context,
+    TransformationContext* /* transformation_context */) const {
+  // Get the OpPhi instruction.
+  auto opphi_def = ir_context->get_def_use_mgr()->GetDef(message_.opphi_id());
+
+  // Find the index corresponding to the operand being replaced and replace it,
+  // by looping through the odd-indexed input operands and finding
+  // |pred_label_id|. The index that we are interested in is the one before
+  // that.
+  for (uint32_t i = 1; i < opphi_def->NumInOperands(); i += 2) {
+    if (opphi_def->GetSingleWordInOperand(i) == message_.pred_label_id()) {
+      // The operand to be replaced is at index i-1.
+      opphi_def->SetInOperand(i - 1, {message_.replacement_id()});
+    }
+  }
+
+  // Invalidate the analyses because we have altered the usages of ids.
+  ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
+}
+
+protobufs::Transformation
+TransformationReplaceOpPhiIdFromDeadPredecessor::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_replace_opphi_id_from_dead_predecessor() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
new file mode 100644
index 0000000..2833eb2
--- /dev/null
+++ b/source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h
@@ -0,0 +1,56 @@
+// 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_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
+#define SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
+
+#include "source/fuzz/transformation.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationReplaceOpPhiIdFromDeadPredecessor : public Transformation {
+ public:
+  explicit TransformationReplaceOpPhiIdFromDeadPredecessor(
+      const protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor&
+          message);
+
+  TransformationReplaceOpPhiIdFromDeadPredecessor(uint32_t opphi_id,
+                                                  uint32_t pred_label_id,
+                                                  uint32_t replacement_id);
+
+  // - |message_.opphi_id| is the id of an OpPhi instruction.
+  // - |message_.pred_label_id| is the label id of one of the predecessors of
+  //   the block containing the OpPhi instruction.
+  // - The predecessor has been recorded as dead.
+  // - |message_.replacement_id| is the id of an instruction with the same type
+  //   as the OpPhi instruction, available at the end of the predecessor.
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // Replaces the id corresponding to predecessor |message_.pred_label_id|, in
+  // the OpPhi instruction |message_.opphi_id|, with |message_.replacement_id|.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  protobufs::TransformationReplaceOpPhiIdFromDeadPredecessor message_;
+};
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_REPLACE_OPPHI_ID_FROM_DEAD_PREDECESSOR_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 105d4a6..34317d0 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -86,6 +86,7 @@
           transformation_replace_irrelevant_id_test.cpp
           transformation_replace_linear_algebra_instruction_test.cpp
           transformation_replace_load_store_with_copy_memory_test.cpp
+          transformation_replace_opphi_id_from_dead_predecessor_test.cpp
           transformation_replace_parameter_with_global_test.cpp
           transformation_replace_params_with_struct_test.cpp
           transformation_set_function_control_test.cpp
diff --git a/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp
new file mode 100644
index 0000000..fd1c180
--- /dev/null
+++ b/test/fuzz/transformation_replace_opphi_id_from_dead_predecessor_test.cpp
@@ -0,0 +1,205 @@
+// Copyright (c) 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "source/fuzz/transformation_replace_opphi_id_from_dead_predecessor.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+std::string shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpConstant %8 2
+         %10 = OpConstant %8 3
+         %11 = OpConstant %8 4
+         %12 = OpConstant %8 5
+         %13 = OpConstant %8 6
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %6 %16 %17
+         %16 = OpLabel
+         %18 = OpCopyObject %8 %9
+               OpSelectionMerge %19 None
+               OpBranchConditional %7 %20 %21
+         %20 = OpLabel
+         %22 = OpCopyObject %8 %10
+         %23 = OpCopyObject %8 %12
+               OpBranch %19
+         %21 = OpLabel
+         %24 = OpCopyObject %8 %9
+               OpBranch %19
+         %19 = OpLabel
+         %25 = OpPhi %8 %22 %20 %24 %21
+               OpBranch %15
+         %17 = OpLabel
+         %26 = OpCopyObject %8 %12
+         %27 = OpCopyObject %8 %13
+               OpBranch %28
+         %28 = OpLabel
+         %29 = OpPhi %8 %27 %17
+               OpBranch %15
+         %15 = OpLabel
+         %30 = OpPhi %8 %25 %19 %26 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Inapplicable) {
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Record the fact that blocks 20, 17, 28 are dead.
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(17);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(28);
+
+  // %26 is not an OpPhi instruction.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(26, 14, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %25 is not a block label.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 25, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %14 is not a predecessor of %28 (which contains %29).
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(29, 14, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %19 is not a dead block.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(30, 19, 10)
+                   .IsApplicable(context.get(), transformation_context));
+
+  // %7 does not have the same type id as %25.
+  ASSERT_FALSE(
+      TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 7).IsApplicable(
+          context.get(), transformation_context));
+
+  // %29 is not available at the end of %20.
+  ASSERT_FALSE(TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 29)
+                   .IsApplicable(context.get(), transformation_context));
+}
+
+TEST(TransformationReplaceOpPhiIdFromDeadPredecessorTest, Apply) {
+  const auto env = SPV_ENV_UNIVERSAL_1_5;
+  const auto consumer = nullptr;
+  const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  // Record the fact that blocks 20, 17, 28 are dead.
+  transformation_context.GetFactManager()->AddFactBlockIsDead(20);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(17);
+  transformation_context.GetFactManager()->AddFactBlockIsDead(28);
+
+  auto transformation1 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(25, 20, 18);
+  ASSERT_TRUE(
+      transformation1.IsApplicable(context.get(), transformation_context));
+  transformation1.Apply(context.get(), &transformation_context);
+
+  auto transformation2 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(30, 28, 29);
+  ASSERT_TRUE(
+      transformation2.IsApplicable(context.get(), transformation_context));
+  transformation2.Apply(context.get(), &transformation_context);
+
+  auto transformation3 =
+      TransformationReplaceOpPhiIdFromDeadPredecessor(29, 17, 10);
+  ASSERT_TRUE(
+      transformation3.IsApplicable(context.get(), transformation_context));
+  transformation3.Apply(context.get(), &transformation_context);
+
+  ASSERT_TRUE(IsValid(env, context.get()));
+
+  std::string after_transformations = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main"
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource ESSL 310
+               OpName %2 "main"
+          %3 = OpTypeVoid
+          %4 = OpTypeFunction %3
+          %5 = OpTypeBool
+          %6 = OpConstantTrue %5
+          %7 = OpConstantFalse %5
+          %8 = OpTypeInt 32 1
+          %9 = OpConstant %8 2
+         %10 = OpConstant %8 3
+         %11 = OpConstant %8 4
+         %12 = OpConstant %8 5
+         %13 = OpConstant %8 6
+          %2 = OpFunction %3 None %4
+         %14 = OpLabel
+               OpSelectionMerge %15 None
+               OpBranchConditional %6 %16 %17
+         %16 = OpLabel
+         %18 = OpCopyObject %8 %9
+               OpSelectionMerge %19 None
+               OpBranchConditional %7 %20 %21
+         %20 = OpLabel
+         %22 = OpCopyObject %8 %10
+         %23 = OpCopyObject %8 %12
+               OpBranch %19
+         %21 = OpLabel
+         %24 = OpCopyObject %8 %9
+               OpBranch %19
+         %19 = OpLabel
+         %25 = OpPhi %8 %18 %20 %24 %21
+               OpBranch %15
+         %17 = OpLabel
+         %26 = OpCopyObject %8 %12
+         %27 = OpCopyObject %8 %13
+               OpBranch %28
+         %28 = OpLabel
+         %29 = OpPhi %8 %10 %17
+               OpBranch %15
+         %15 = OpLabel
+         %30 = OpPhi %8 %25 %19 %29 %28
+               OpReturn
+               OpFunctionEnd
+)";
+
+  ASSERT_TRUE(IsEqual(env, after_transformations, context.get()));
+}
+
+}  // namespace
+}  // namespace fuzz
+}  // namespace spvtools