spirv-fuzz: Add support for OpSpecConstant* (#3373)

Fixes #3371.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 1fadd8f..efd82e4 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -90,6 +90,7 @@
         transformation_add_global_variable.h
         transformation_add_local_variable.h
         transformation_add_no_contraction_decoration.h
+        transformation_add_spec_constant_op.h
         transformation_add_type_array.h
         transformation_add_type_boolean.h
         transformation_add_type_float.h
@@ -187,6 +188,7 @@
         transformation_add_global_variable.cpp
         transformation_add_local_variable.cpp
         transformation_add_no_contraction_decoration.cpp
+        transformation_add_spec_constant_op.cpp
         transformation_add_type_array.cpp
         transformation_add_type_boolean.cpp
         transformation_add_type_float.cpp
diff --git a/source/fuzz/fuzzer_pass_donate_modules.cpp b/source/fuzz/fuzzer_pass_donate_modules.cpp
index c06a928..ffccd3c 100644
--- a/source/fuzz/fuzzer_pass_donate_modules.cpp
+++ b/source/fuzz/fuzzer_pass_donate_modules.cpp
@@ -27,6 +27,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_spec_constant_op.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
@@ -413,14 +414,41 @@
             argument_type_ids));
       }
     } break;
+    case SpvOpSpecConstantOp: {
+      new_result_id = GetFuzzerContext()->GetFreshId();
+      auto type_id = original_id_to_donated_id->at(type_or_value.type_id());
+      auto opcode = static_cast<SpvOp>(type_or_value.GetSingleWordInOperand(0));
+
+      // Make sure we take into account |original_id_to_donated_id| when
+      // computing operands for OpSpecConstantOp.
+      opt::Instruction::OperandList operands;
+      for (uint32_t i = 1; i < type_or_value.NumInOperands(); ++i) {
+        const auto& operand = type_or_value.GetInOperand(i);
+        auto data =
+            operand.type == SPV_OPERAND_TYPE_ID
+                ? opt::Operand::OperandData{original_id_to_donated_id->at(
+                      operand.words[0])}
+                : operand.words;
+
+        operands.push_back({operand.type, std::move(data)});
+      }
+
+      ApplyTransformation(TransformationAddSpecConstantOp(
+          new_result_id, type_id, opcode, std::move(operands)));
+    } break;
+    case SpvOpSpecConstantTrue:
+    case SpvOpSpecConstantFalse:
     case SpvOpConstantTrue:
     case SpvOpConstantFalse: {
       // It is OK to have duplicate definitions of True and False, so add
       // these to the module, using a remapped Bool type.
       new_result_id = GetFuzzerContext()->GetFreshId();
-      ApplyTransformation(TransformationAddConstantBoolean(
-          new_result_id, type_or_value.opcode() == SpvOpConstantTrue));
+      auto value = type_or_value.opcode() == SpvOpConstantTrue ||
+                   type_or_value.opcode() == SpvOpSpecConstantTrue;
+      ApplyTransformation(
+          TransformationAddConstantBoolean(new_result_id, value));
     } break;
+    case SpvOpSpecConstant:
     case SpvOpConstant: {
       // It is OK to have duplicate constant definitions, so add this to the
       // module using a remapped result type.
@@ -433,6 +461,7 @@
           new_result_id, original_id_to_donated_id->at(type_or_value.type_id()),
           data_words));
     } break;
+    case SpvOpSpecConstantComposite:
     case SpvOpConstantComposite: {
       assert(original_id_to_donated_id->count(type_or_value.type_id()) &&
              "Composite types for which it is possible to create a constant "
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 645b733..052af16 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -376,6 +376,7 @@
     TransformationComputeDataSynonymFactClosure compute_data_synonym_fact_closure = 45;
     TransformationAdjustBranchWeights adjust_branch_weights = 46;
     TransformationPushIdThroughVariable push_id_through_variable = 47;
+    TransformationAddSpecConstantOp add_spec_constant_op = 48;
     // Add additional option using the next available number.
   }
 }
@@ -619,6 +620,24 @@
 
 }
 
+message TransformationAddSpecConstantOp {
+
+  // Adds OpSpecConstantOp into the module.
+
+  // Result id for the new instruction.
+  uint32 fresh_id = 1;
+
+  // Type id for the new instruction.
+  uint32 type_id = 2;
+
+  // Opcode operand of the OpSpecConstantOp instruction.
+  uint32 opcode = 3;
+
+  // Operands of the |opcode| instruction.
+  repeated InstructionOperand operand = 4;
+
+}
+
 message TransformationAddTypeArray {
 
   // Adds an array type of the given element type and size to the module
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index 42168cb..71e2348 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -30,6 +30,7 @@
 #include "source/fuzz/transformation_add_global_variable.h"
 #include "source/fuzz/transformation_add_local_variable.h"
 #include "source/fuzz/transformation_add_no_contraction_decoration.h"
+#include "source/fuzz/transformation_add_spec_constant_op.h"
 #include "source/fuzz/transformation_add_type_array.h"
 #include "source/fuzz/transformation_add_type_boolean.h"
 #include "source/fuzz/transformation_add_type_float.h"
@@ -110,6 +111,9 @@
         kAddNoContractionDecoration:
       return MakeUnique<TransformationAddNoContractionDecoration>(
           message.add_no_contraction_decoration());
+    case protobufs::Transformation::TransformationCase::kAddSpecConstantOp:
+      return MakeUnique<TransformationAddSpecConstantOp>(
+          message.add_spec_constant_op());
     case protobufs::Transformation::TransformationCase::kAddTypeArray:
       return MakeUnique<TransformationAddTypeArray>(message.add_type_array());
     case protobufs::Transformation::TransformationCase::kAddTypeBoolean:
diff --git a/source/fuzz/transformation_add_spec_constant_op.cpp b/source/fuzz/transformation_add_spec_constant_op.cpp
new file mode 100644
index 0000000..d6a7083
--- /dev/null
+++ b/source/fuzz/transformation_add_spec_constant_op.cpp
@@ -0,0 +1,84 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 <utility>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_add_spec_constant_op.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationAddSpecConstantOp::TransformationAddSpecConstantOp(
+    spvtools::fuzz::protobufs::TransformationAddSpecConstantOp message)
+    : message_(std::move(message)) {}
+
+TransformationAddSpecConstantOp::TransformationAddSpecConstantOp(
+    uint32_t fresh_id, uint32_t type_id, SpvOp opcode,
+    const opt::Instruction::OperandList& operands) {
+  message_.set_fresh_id(fresh_id);
+  message_.set_type_id(type_id);
+  message_.set_opcode(opcode);
+  for (const auto& operand : operands) {
+    auto* op = message_.add_operand();
+    op->set_operand_type(operand.type);
+    for (auto word : operand.words) {
+      op->add_operand_data(word);
+    }
+  }
+}
+
+bool TransformationAddSpecConstantOp::IsApplicable(
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
+  auto clone = fuzzerutil::CloneIRContext(ir_context);
+  ApplyImpl(clone.get());
+  return fuzzerutil::IsValid(clone.get(),
+                             transformation_context.GetValidatorOptions());
+}
+
+void TransformationAddSpecConstantOp::Apply(
+    opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
+  ApplyImpl(ir_context);
+  ir_context->InvalidateAnalysesExceptFor(
+      opt::IRContext::Analysis::kAnalysisNone);
+}
+
+void TransformationAddSpecConstantOp::ApplyImpl(
+    opt::IRContext* ir_context) const {
+  opt::Instruction::OperandList operands = {
+      {SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER, {message_.opcode()}}};
+
+  for (const auto& operand : message_.operand()) {
+    std::vector<uint32_t> words(operand.operand_data().begin(),
+                                operand.operand_data().end());
+    operands.push_back({static_cast<spv_operand_type_t>(operand.operand_type()),
+                        std::move(words)});
+  }
+
+  ir_context->AddGlobalValue(MakeUnique<opt::Instruction>(
+      ir_context, SpvOpSpecConstantOp, message_.type_id(), message_.fresh_id(),
+      std::move(operands)));
+
+  fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id());
+}
+
+protobufs::Transformation TransformationAddSpecConstantOp::ToMessage() const {
+  protobufs::Transformation result;
+  *result.mutable_add_spec_constant_op() = message_;
+  return result;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/transformation_add_spec_constant_op.h b/source/fuzz/transformation_add_spec_constant_op.h
new file mode 100644
index 0000000..c0f7154
--- /dev/null
+++ b/source/fuzz/transformation_add_spec_constant_op.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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_SPEC_CONSTANT_OP_H_
+#define SOURCE_FUZZ_TRANSFORMATION_ADD_SPEC_CONSTANT_OP_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 TransformationAddSpecConstantOp : public Transformation {
+ public:
+  explicit TransformationAddSpecConstantOp(
+      protobufs::TransformationAddSpecConstantOp message);
+
+  TransformationAddSpecConstantOp(
+      uint32_t fresh_id, uint32_t type_id, SpvOp opcode,
+      const opt::Instruction::OperandList& operands);
+
+  // - |fresh_id| is a fresh result id in the module.
+  // - |type_id| is a valid result id of some OpType* instruction in the
+  // module. It is also a valid type id with respect to |opcode|.
+  // - |opcode| is one of the opcodes supported by OpSpecConstantOp.
+  // - |operands| are valid with respect to |opcode|
+  bool IsApplicable(
+      opt::IRContext* ir_context,
+      const TransformationContext& transformation_context) const override;
+
+  // |%fresh_id = OpSpecConstantOp %type_id opcode operands...| is added to the
+  // module.
+  void Apply(opt::IRContext* ir_context,
+             TransformationContext* transformation_context) const override;
+
+  protobufs::Transformation ToMessage() const override;
+
+ private:
+  void ApplyImpl(opt::IRContext* ir_context) const;
+
+  protobufs::TransformationAddSpecConstantOp message_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_TRANSFORMATION_ADD_SPEC_CONSTANT_OP_H_
diff --git a/test/fuzz/fuzzer_pass_donate_modules_test.cpp b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
index 0833c1d..8d258c2 100644
--- a/test/fuzz/fuzzer_pass_donate_modules_test.cpp
+++ b/test/fuzz/fuzzer_pass_donate_modules_test.cpp
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include <algorithm>
+
 #include "source/fuzz/fuzzer_pass_donate_modules.h"
 #include "source/fuzz/pseudo_random_generator.h"
 #include "test/fuzz/fuzz_test_util.h"
@@ -1678,6 +1680,104 @@
   ASSERT_TRUE(IsValid(env, recipient_context.get()));
 }
 
+TEST(FuzzerPassDonateModulesTest, OpSpecConstantInstructions) {
+  std::string donor_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %6 = OpTypeBool
+          %7 = OpTypeInt 32 1
+          %8 = OpTypeStruct %6 %6 %7
+          %9 = OpSpecConstantTrue %6
+         %10 = OpSpecConstantFalse %6
+         %11 = OpSpecConstant %7 2
+         %12 = OpSpecConstantComposite %8 %9 %10 %11
+         %13 = OpSpecConstantOp %6 LogicalEqual %9 %10
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  std::string recipient_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  const auto env = SPV_ENV_UNIVERSAL_1_3;
+  const auto consumer = nullptr;
+  const auto recipient_context =
+      BuildModule(env, consumer, recipient_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  const auto donor_context =
+      BuildModule(env, consumer, donor_shader, kFuzzAssembleOption);
+  ASSERT_TRUE(IsValid(env, donor_context.get()));
+
+  FactManager fact_manager;
+  spvtools::ValidatorOptions validator_options;
+  TransformationContext transformation_context(&fact_manager,
+                                               validator_options);
+
+  PseudoRandomGenerator prng(0);
+  FuzzerContext fuzzer_context(&prng, 100);
+  protobufs::TransformationSequence transformation_sequence;
+
+  FuzzerPassDonateModules fuzzer_pass(recipient_context.get(),
+                                      &transformation_context, &fuzzer_context,
+                                      &transformation_sequence, {});
+
+  fuzzer_pass.DonateSingleModule(donor_context.get(), false);
+
+  // Check that the module is valid first.
+  ASSERT_TRUE(IsValid(env, recipient_context.get()));
+
+  std::string expected_shader = R"(
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %4 "main"
+               OpExecutionMode %4 OriginUpperLeft
+               OpSource ESSL 310
+          %2 = OpTypeVoid
+          %3 = OpTypeFunction %2
+        %100 = OpTypeBool
+        %101 = OpTypeInt 32 1
+        %102 = OpTypeStruct %100 %100 %101
+        %103 = OpConstantTrue %100
+        %104 = OpConstantFalse %100
+        %105 = OpConstant %101 2
+        %106 = OpConstantComposite %102 %103 %104 %105
+        %107 = OpSpecConstantOp %100 LogicalEqual %103 %104
+          %4 = OpFunction %2 None %3
+          %5 = OpLabel
+               OpReturn
+               OpFunctionEnd
+        %108 = OpFunction %2 None %3
+        %109 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  // Now check that the transformation has produced the expected result.
+  ASSERT_TRUE(IsEqual(env, expected_shader, recipient_context.get()));
+}
+
 }  // namespace
 }  // namespace fuzz
 }  // namespace spvtools