spirv-fuzz: Overflow ids (#3734)

This change adds the notion of "overflow ids", which can be used
during shrinking to facilitate applying transformations that would
otherwise have become inapplicable due to earlier transformations
being removed.
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 2566b56..ab7e985 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -30,6 +30,7 @@
 
   set(SPIRV_TOOLS_FUZZ_SOURCES
         call_graph.h
+        counter_overflow_id_source.h
         data_descriptor.h
         equivalence_relation.h
         fact_manager/constant_uniform_facts.h
@@ -100,6 +101,7 @@
         id_use_descriptor.h
         instruction_descriptor.h
         instruction_message.h
+        overflow_id_source.h
         protobufs/spirvfuzz_protobufs.h
         pseudo_random_generator.h
         random_generator.h
@@ -180,6 +182,7 @@
         ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h
 
         call_graph.cpp
+        counter_overflow_id_source.cpp
         data_descriptor.cpp
         fact_manager/constant_uniform_facts.cpp
         fact_manager/data_synonym_and_id_equation_facts.cpp
@@ -249,6 +252,7 @@
         id_use_descriptor.cpp
         instruction_descriptor.cpp
         instruction_message.cpp
+        overflow_id_source.cpp
         pseudo_random_generator.cpp
         random_generator.cpp
         replayer.cpp
diff --git a/source/fuzz/counter_overflow_id_source.cpp b/source/fuzz/counter_overflow_id_source.cpp
new file mode 100644
index 0000000..1ed5603
--- /dev/null
+++ b/source/fuzz/counter_overflow_id_source.cpp
@@ -0,0 +1,30 @@
+// 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/counter_overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+CounterOverflowIdSource::CounterOverflowIdSource(uint32_t first_available_id)
+    : next_available_id_(first_available_id) {}
+
+bool CounterOverflowIdSource::HasOverflowIds() const { return true; }
+
+uint32_t CounterOverflowIdSource::GetNextOverflowId() {
+  return next_available_id_++;
+}
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/counter_overflow_id_source.h b/source/fuzz/counter_overflow_id_source.h
new file mode 100644
index 0000000..aa8bc7a
--- /dev/null
+++ b/source/fuzz/counter_overflow_id_source.h
@@ -0,0 +1,45 @@
+// 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_COUNTER_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_COUNTER_OVERFLOW_ID_SOURCE_H_
+
+#include "source/fuzz/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// A source of overflow ids that uses a counter to provide successive ids from
+// a given starting value.
+class CounterOverflowIdSource : public OverflowIdSource {
+ public:
+  // |first_available_id| is the starting value for the counter.
+  explicit CounterOverflowIdSource(uint32_t first_available_id);
+
+  // Always returns true.
+  bool HasOverflowIds() const override;
+
+  // Returns the current counter value and increments the counter.
+  // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2541) We should
+  //  account for the case where the maximum allowed id is reached.
+  uint32_t GetNextOverflowId() override;
+
+ private:
+  uint32_t next_available_id_;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_COUNTER_H_
diff --git a/source/fuzz/overflow_id_source.cpp b/source/fuzz/overflow_id_source.cpp
new file mode 100644
index 0000000..d900455
--- /dev/null
+++ b/source/fuzz/overflow_id_source.cpp
@@ -0,0 +1,23 @@
+// 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/overflow_id_source.h"
+
+namespace spvtools {
+namespace fuzz {
+
+OverflowIdSource::~OverflowIdSource() = default;
+
+}  // namespace fuzz
+}  // namespace spvtools
diff --git a/source/fuzz/overflow_id_source.h b/source/fuzz/overflow_id_source.h
new file mode 100644
index 0000000..b428fa5
--- /dev/null
+++ b/source/fuzz/overflow_id_source.h
@@ -0,0 +1,106 @@
+// 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_OVERFLOW_ID_SOURCE_H_
+#define SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
+
+#include <cstdint>
+
+namespace spvtools {
+namespace fuzz {
+
+// An implementation of this interface can be used to provide fresh ids on
+// demand when applying a transformation.
+//
+// During fuzzing this should never be required: a fuzzer pass should determine
+// all the fresh ids it requires to apply a transformation.
+//
+// However, during shrinking we can have the situation where, after removing
+// an early transformation, a later transformation needs more ids.
+//
+// As an example, suppose a SPIR-V function originally has this form:
+//
+// main() {
+//   stmt1;
+//   stmt2;
+//   stmt3;
+//   stmt4;
+// }
+//
+// Now suppose two *outlining* transformations are applied.  The first
+// transformation, T1, outlines "stmt1; stmt2;" into a function foo, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// main() {
+//   foo();
+//   stmt3;
+//   stmt4;
+// }
+//
+// The second transformation, T2, outlines "foo(); stmt3;" from main into a
+// function bar, giving us:
+//
+// foo() {
+//   stmt1;
+//   stmt2;
+// }
+//
+// bar() {
+//   foo();
+//   stmt3;
+// }
+//
+// main() {
+//   bar();
+//   stmt4;
+// }
+//
+// Suppose that T2 used a set of fresh ids, FRESH, in order to perform its
+// outlining.
+//
+// Now suppose that during shrinking we remove T1, but still want to apply T2.
+// The fresh ids used by T2 - FRESH - are sufficient to outline "foo(); stmt3;".
+// However, because we did not apply T1, "foo();" does not exist and instead the
+// task of T2 is to outline "stmt1; stmt2; stmt3;".  The set FRESH contains
+// *some* of the fresh ids required to do this (those for "stmt3;"), but not all
+// of them (those for "stmt1; stmt2;" are missing).
+//
+// A source of overflow ids can be used to allow the shrinker to proceed
+// nevertheless.
+//
+// It is desirable to use overflow ids only when needed.  In our worked example,
+// T2 should still use the ids from FRESH when handling "stmt3;", because later
+// transformations might refer to those ids and will become inapplicable if
+// overflow ids are used instead.
+class OverflowIdSource {
+ public:
+  virtual ~OverflowIdSource();
+
+  // Returns true if and only if this source is capable of providing overflow
+  // ids.
+  virtual bool HasOverflowIds() const = 0;
+
+  // Precondition: HasOverflowIds() must hold.  Returns the next available
+  // overflow id.
+  virtual uint32_t GetNextOverflowId() = 0;
+};
+
+}  // namespace fuzz
+}  // namespace spvtools
+
+#endif  // SOURCE_FUZZ_OVERFLOW_ID_SOURCE_H_
diff --git a/source/fuzz/replayer.cpp b/source/fuzz/replayer.cpp
index bfc5536..3a49f55 100644
--- a/source/fuzz/replayer.cpp
+++ b/source/fuzz/replayer.cpp
@@ -14,8 +14,10 @@
 
 #include "source/fuzz/replayer.h"
 
+#include <memory>
 #include <utility>
 
+#include "source/fuzz/counter_overflow_id_source.h"
 #include "source/fuzz/fact_manager/fact_manager.h"
 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
 #include "source/fuzz/transformation.h"
@@ -54,7 +56,8 @@
     const std::vector<uint32_t>& binary_in,
     const protobufs::FactSequence& initial_facts,
     const protobufs::TransformationSequence& transformation_sequence_in,
-    uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
+    uint32_t num_transformations_to_apply, uint32_t first_overflow_id,
+    std::vector<uint32_t>* binary_out,
     protobufs::TransformationSequence* transformation_sequence_out) const {
   // Check compatibility between the library version being linked with and the
   // header files being used.
@@ -97,8 +100,18 @@
 
   FactManager fact_manager;
   fact_manager.AddFacts(impl_->consumer, initial_facts, ir_context.get());
-  TransformationContext transformation_context(&fact_manager,
-                                               impl_->validator_options);
+  std::unique_ptr<TransformationContext> transformation_context =
+      first_overflow_id == 0
+          ? MakeUnique<TransformationContext>(&fact_manager,
+                                              impl_->validator_options)
+          : MakeUnique<TransformationContext>(
+                &fact_manager, impl_->validator_options,
+                MakeUnique<CounterOverflowIdSource>(first_overflow_id));
+
+  // We track the largest id bound observed, to ensure that it only increases
+  // as transformations are applied.
+  uint32_t max_observed_id_bound = ir_context->module()->id_bound();
+  (void)(max_observed_id_bound);  // Keep release-mode compilers happy.
 
   // Consider the transformation proto messages in turn.
   uint32_t counter = 0;
@@ -112,12 +125,17 @@
 
     // Check whether the transformation can be applied.
     if (transformation->IsApplicable(ir_context.get(),
-                                     transformation_context)) {
+                                     *transformation_context)) {
       // The transformation is applicable, so apply it, and copy it to the
       // sequence of transformations that were applied.
-      transformation->Apply(ir_context.get(), &transformation_context);
+      transformation->Apply(ir_context.get(), transformation_context.get());
       *transformation_sequence_out->add_transformation() = message;
 
+      assert(ir_context->module()->id_bound() >= max_observed_id_bound &&
+             "The module's id bound should only increase due to applying "
+             "transformations.");
+      max_observed_id_bound = ir_context->module()->id_bound();
+
       if (impl_->validate_during_replay) {
         std::vector<uint32_t> binary_to_validate;
         ir_context->module()->ToBinary(&binary_to_validate, false);
diff --git a/source/fuzz/replayer.h b/source/fuzz/replayer.h
index d6395aa..23e39d0 100644
--- a/source/fuzz/replayer.h
+++ b/source/fuzz/replayer.h
@@ -55,15 +55,24 @@
 
   // Transforms |binary_in| to |binary_out| by attempting to apply the first
   // |num_transformations_to_apply| transformations from
-  // |transformation_sequence_in|.  Initial facts about the input binary and the
-  // context in which it will execute are provided via |initial_facts|.  The
-  // transformations that were successfully applied are returned via
+  // |transformation_sequence_in|.
+  //
+  // Initial facts about the input binary and the context in which it will
+  // execute are provided via |initial_facts|.
+  //
+  // |first_overflow_id| should be set to 0 if overflow ids are not available
+  // during replay.  Otherwise |first_overflow_id| must be larger than any id
+  // referred to in |binary_in| or |transformation_sequence_in|, and overflow
+  // ids will be available during replay starting from this value.
+  //
+  // The transformations that were successfully applied are returned via
   // |transformation_sequence_out|.
   ReplayerResultStatus Run(
       const std::vector<uint32_t>& binary_in,
       const protobufs::FactSequence& initial_facts,
       const protobufs::TransformationSequence& transformation_sequence_in,
-      uint32_t num_transformations_to_apply, std::vector<uint32_t>* binary_out,
+      uint32_t num_transformations_to_apply, uint32_t first_overflow_id,
+      std::vector<uint32_t>* binary_out,
       protobufs::TransformationSequence* transformation_sequence_out) const;
 
  private:
diff --git a/source/fuzz/shrinker.cpp b/source/fuzz/shrinker.cpp
index 829df63..dcdf54a 100644
--- a/source/fuzz/shrinker.cpp
+++ b/source/fuzz/shrinker.cpp
@@ -18,6 +18,8 @@
 
 #include "source/fuzz/pseudo_random_generator.h"
 #include "source/fuzz/replayer.h"
+#include "source/opt/build_module.h"
+#include "source/opt/ir_context.h"
 #include "source/spirv_fuzzer_options.h"
 #include "source/util/make_unique.h"
 
@@ -67,6 +69,10 @@
         validate_during_replay(validate),
         validator_options(options) {}
 
+  // Returns the id bound for the given SPIR-V binary, which is assumed to be
+  // valid.
+  uint32_t GetIdBound(const std::vector<uint32_t>& binary);
+
   const spv_target_env target_env;          // Target environment.
   MessageConsumer consumer;                 // Message consumer.
   const uint32_t step_limit;                // Step limit for reductions.
@@ -76,6 +82,14 @@
   spv_validator_options validator_options;  // Options to control validation.
 };
 
+uint32_t Shrinker::Impl::GetIdBound(const std::vector<uint32_t>& binary) {
+  // Build the module from the input binary.
+  std::unique_ptr<opt::IRContext> ir_context =
+      BuildModule(target_env, consumer, binary.data(), binary.size());
+  assert(ir_context && "Error building module.");
+  return ir_context->module()->id_bound();
+}
+
 Shrinker::Shrinker(spv_target_env env, uint32_t step_limit,
                    bool validate_during_replay,
                    spv_validator_options validator_options)
@@ -99,7 +113,7 @@
   // header files being used.
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
-  spvtools::SpirvTools tools(impl_->target_env);
+  SpirvTools tools(impl_->target_env);
   if (!tools.IsValid()) {
     impl_->consumer(SPV_MSG_ERROR, nullptr, {},
                     "Failed to create SPIRV-Tools interface; stopping.");
@@ -127,7 +141,8 @@
   if (replayer.Run(binary_in, initial_facts, transformation_sequence_in,
                    static_cast<uint32_t>(
                        transformation_sequence_in.transformation_size()),
-                   &current_best_binary, &current_best_transformations) !=
+                   /* No overflow ids */ 0, &current_best_binary,
+                   &current_best_transformations) !=
       Replayer::ReplayerResultStatus::kComplete) {
     return ShrinkerResultStatus::kReplayFailed;
   }
@@ -140,6 +155,12 @@
     return ShrinkerResultStatus::kInitialBinaryNotInteresting;
   }
 
+  // The largest id used by the module before any shrinking has been applied
+  // serves as the first id that can be used for overflow purposes.
+  const uint32_t first_overflow_id = impl_->GetIdBound(current_best_binary);
+  assert(first_overflow_id >= impl_->GetIdBound(binary_in) &&
+         "Applying transformations should only increase a module's id bound.");
+
   uint32_t attempt = 0;  // Keeps track of the number of shrink attempts that
                          // have been tried, whether successful or not.
 
@@ -201,7 +222,7 @@
               binary_in, initial_facts, transformations_with_chunk_removed,
               static_cast<uint32_t>(
                   transformations_with_chunk_removed.transformation_size()),
-              &next_binary, &next_transformation_sequence) !=
+              first_overflow_id, &next_binary, &next_transformation_sequence) !=
           Replayer::ReplayerResultStatus::kComplete) {
         // Replay should not fail; if it does, we need to abort shrinking.
         return ShrinkerResultStatus::kReplayFailed;
diff --git a/source/fuzz/transformation_context.cpp b/source/fuzz/transformation_context.cpp
index 6c2dfdf..bd0e406 100644
--- a/source/fuzz/transformation_context.cpp
+++ b/source/fuzz/transformation_context.cpp
@@ -14,12 +14,43 @@
 
 #include "source/fuzz/transformation_context.h"
 
+#include <cassert>
+
+#include "source/util/make_unique.h"
+
 namespace spvtools {
 namespace fuzz {
+namespace {
+
+// An overflow id source that should never be used: its methods assert false.
+// This is the right id source for use during fuzzing, when overflow ids should
+// never be required.
+class NullOverflowIdSource : public OverflowIdSource {
+  bool HasOverflowIds() const override {
+    assert(false && "Bad attempt to query whether overflow ids are available.");
+    return false;
+  }
+
+  uint32_t GetNextOverflowId() override {
+    assert(false && "Bad attempt to request an overflow id.");
+    return 0;
+  }
+};
+
+}  // namespace
 
 TransformationContext::TransformationContext(
     FactManager* fact_manager, spv_validator_options validator_options)
-    : fact_manager_(fact_manager), validator_options_(validator_options) {}
+    : fact_manager_(fact_manager),
+      validator_options_(validator_options),
+      overflow_id_source_(MakeUnique<NullOverflowIdSource>()) {}
+
+TransformationContext::TransformationContext(
+    FactManager* fact_manager, spv_validator_options validator_options,
+    std::unique_ptr<OverflowIdSource> overflow_id_source)
+    : fact_manager_(fact_manager),
+      validator_options_(validator_options),
+      overflow_id_source_(std::move(overflow_id_source)) {}
 
 TransformationContext::~TransformationContext() = default;
 
diff --git a/source/fuzz/transformation_context.h b/source/fuzz/transformation_context.h
index 400e0f5..c76a7be 100644
--- a/source/fuzz/transformation_context.h
+++ b/source/fuzz/transformation_context.h
@@ -15,7 +15,10 @@
 #ifndef SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 #define SOURCE_FUZZ_TRANSFORMATION_CONTEXT_H_
 
+#include <memory>
+
 #include "source/fuzz/fact_manager/fact_manager.h"
+#include "source/fuzz/overflow_id_source.h"
 #include "spirv-tools/libspirv.hpp"
 
 namespace spvtools {
@@ -26,16 +29,29 @@
 class TransformationContext {
  public:
   // Constructs a transformation context with a given fact manager and validator
-  // options.
+  // options.  Overflow ids are not available from a transformation context
+  // constructed in this way.
   TransformationContext(FactManager* fact_manager,
                         spv_validator_options validator_options);
 
+  // Constructs a transformation context with a given fact manager, validator
+  // options and overflow id source.
+  TransformationContext(FactManager* fact_manager,
+                        spv_validator_options validator_options,
+                        std::unique_ptr<OverflowIdSource> overflow_id_source);
+
   ~TransformationContext();
 
   FactManager* GetFactManager() { return fact_manager_; }
 
   const FactManager* GetFactManager() const { return fact_manager_; }
 
+  OverflowIdSource* GetOverflowIdSource() { return overflow_id_source_.get(); }
+
+  const OverflowIdSource* GetOverflowIdSource() const {
+    return overflow_id_source_.get();
+  }
+
   spv_validator_options GetValidatorOptions() const {
     return validator_options_;
   }
@@ -48,6 +64,8 @@
   // Options to control validation when deciding whether transformations can be
   // applied.
   spv_validator_options validator_options_;
+
+  std::unique_ptr<OverflowIdSource> overflow_id_source_;
 };
 
 }  // namespace fuzz
diff --git a/source/fuzz/transformation_outline_function.cpp b/source/fuzz/transformation_outline_function.cpp
index 9257209..8534507 100644
--- a/source/fuzz/transformation_outline_function.cpp
+++ b/source/fuzz/transformation_outline_function.cpp
@@ -48,7 +48,8 @@
 }
 
 bool TransformationOutlineFunction::IsApplicable(
-    opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+    opt::IRContext* ir_context,
+    const TransformationContext& transformation_context) const {
   std::set<uint32_t> ids_used_by_this_transformation;
 
   // The various new ids used by the transformation must be fresh and distinct.
@@ -234,8 +235,9 @@
       fuzzerutil::RepeatedUInt32PairToMap(message_.input_id_to_fresh_id());
   for (auto id : GetRegionInputIds(ir_context, region_set, exit_block)) {
     // There needs to be a corresponding fresh id to be used as a function
-    // parameter.
-    if (input_id_to_fresh_id_map.count(id) == 0) {
+    // parameter, or overflow ids need to be available.
+    if (input_id_to_fresh_id_map.count(id) == 0 &&
+        !transformation_context.GetOverflowIdSource()->HasOverflowIds()) {
       return false;
     }
     // Furthermore, if the input id has pointer type it must be an OpVariable
@@ -263,8 +265,10 @@
   for (auto id : GetRegionOutputIds(ir_context, region_set, exit_block)) {
     if (
         // ... there needs to be a corresponding fresh id that can hold the
-        // value for this id computed in the outlined function, and ...
-        output_id_to_fresh_id_map.count(id) == 0
+        // value for this id computed in the outlined function (or overflow ids
+        // must be available), and ...
+        (output_id_to_fresh_id_map.count(id) == 0 &&
+         !transformation_context.GetOverflowIdSource()->HasOverflowIds())
         // ... the output id must not have pointer type (to avoid creating a
         // struct with pointer members to pass data out of the outlined
         // function)
@@ -306,6 +310,23 @@
   auto output_id_to_fresh_id_map =
       fuzzerutil::RepeatedUInt32PairToMap(message_.output_id_to_fresh_id());
 
+  // Use overflow ids to augment these maps at any locations where fresh ids are
+  // required but not provided.
+  for (uint32_t id : region_input_ids) {
+    if (input_id_to_fresh_id_map.count(id) == 0) {
+      input_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
+  for (uint32_t id : region_output_ids) {
+    if (output_id_to_fresh_id_map.count(id) == 0) {
+      output_id_to_fresh_id_map.insert(
+          {id,
+           transformation_context->GetOverflowIdSource()->GetNextOverflowId()});
+    }
+  }
+
   UpdateModuleIdBoundForFreshIds(ir_context, input_id_to_fresh_id_map,
                                  output_id_to_fresh_id_map);
 
@@ -609,7 +630,7 @@
                 {function_type_id}}})));
 
   // Add one parameter to the function for each input id, using the fresh ids
-  // provided in |input_id_to_fresh_id_map|.
+  // provided in |input_id_to_fresh_id_map|, or overflow ids if needed.
   for (auto id : region_input_ids) {
     outlined_function->AddParameter(MakeUnique<opt::Instruction>(
         ir_context, SpvOpFunctionParameter,
diff --git a/test/fuzz/fuzzer_replayer_test.cpp b/test/fuzz/fuzzer_replayer_test.cpp
index 8a23574..615f115 100644
--- a/test/fuzz/fuzzer_replayer_test.cpp
+++ b/test/fuzz/fuzzer_replayer_test.cpp
@@ -1661,7 +1661,7 @@
         binary_in, initial_facts, fuzzer_transformation_sequence_out,
         static_cast<uint32_t>(
             fuzzer_transformation_sequence_out.transformation_size()),
-        &replayer_binary_out, &replayer_transformation_sequence_out);
+        0, &replayer_binary_out, &replayer_transformation_sequence_out);
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
               replayer_result_status);
 
diff --git a/test/fuzz/replayer_test.cpp b/test/fuzz/replayer_test.cpp
index 5d6169e..39787d2 100644
--- a/test/fuzz/replayer_test.cpp
+++ b/test/fuzz/replayer_test.cpp
@@ -95,8 +95,8 @@
     Replayer replayer(env, true, validator_options);
     replayer.SetMessageConsumer(kSilentConsumer);
     auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 11, &binary_out,
-                     &transformations_out);
+        replayer.Run(binary_in, empty_facts, transformations, 11, 0,
+                     &binary_out, &transformations_out);
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
               replayer_result_status);
@@ -183,7 +183,7 @@
     Replayer replayer(env, true, validator_options);
     replayer.SetMessageConsumer(kSilentConsumer);
     auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 5, &binary_out,
+        replayer.Run(binary_in, empty_facts, transformations, 5, 0, &binary_out,
                      &transformations_out);
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
@@ -263,7 +263,7 @@
     Replayer replayer(env, true, validator_options);
     replayer.SetMessageConsumer(kSilentConsumer);
     auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 0, &binary_out,
+        replayer.Run(binary_in, empty_facts, transformations, 0, 0, &binary_out,
                      &transformations_out);
     // Replay should succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kComplete,
@@ -283,8 +283,8 @@
     Replayer replayer(env, true, validator_options);
     replayer.SetMessageConsumer(kSilentConsumer);
     auto replayer_result_status =
-        replayer.Run(binary_in, empty_facts, transformations, 12, &binary_out,
-                     &transformations_out);
+        replayer.Run(binary_in, empty_facts, transformations, 12, 0,
+                     &binary_out, &transformations_out);
 
     // Replay should not succeed.
     ASSERT_EQ(Replayer::ReplayerResultStatus::kTooManyTransformationsRequested,
diff --git a/test/fuzz/transformation_outline_function_test.cpp b/test/fuzz/transformation_outline_function_test.cpp
index ed4fd15..bbce7e3 100644
--- a/test/fuzz/transformation_outline_function_test.cpp
+++ b/test/fuzz/transformation_outline_function_test.cpp
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include "source/fuzz/transformation_outline_function.h"
+
+#include "source/fuzz/counter_overflow_id_source.h"
 #include "test/fuzz/fuzz_test_util.h"
 
 namespace spvtools {
@@ -2579,9 +2581,10 @@
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous1) {
-  // This tests outlining of some non-trivial code.
+  // This tests outlining of some non-trivial code, and also tests the way
+  // overflow ids are used by the transformation.
 
-  std::string shader = R"(
+  std::string reference_shader = R"(
                OpCapability Shader
           %1 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
@@ -2682,13 +2685,7 @@
 
   const auto env = SPV_ENV_UNIVERSAL_1_3;
   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);
 
   TransformationOutlineFunction transformation(
       /*entry_block*/ 150,
@@ -2702,123 +2699,317 @@
       /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}},
       /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}});
 
-  ASSERT_TRUE(
-      transformation.IsApplicable(context.get(), transformation_context));
-  transformation.Apply(context.get(), &transformation_context);
-  ASSERT_TRUE(IsValid(env, context.get()));
+  TransformationOutlineFunction transformation_with_missing_input_id(
+      /*entry_block*/ 150,
+      /*exit_block*/ 1001,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 203,
+      /*new_caller_result_id*/ 204,
+      /*new_callee_result_id*/ 205,
+      /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}},
+      /*output_id_to_fresh_id*/ {{106, 400}, {107, 401}});
 
-  std::string after_transformation = R"(
-               OpCapability Shader
-          %1 = OpExtInstImport "GLSL.std.450"
-               OpMemoryModel Logical GLSL450
-               OpEntryPoint Fragment %4 "main" %85
-               OpExecutionMode %4 OriginUpperLeft
-               OpSource ESSL 310
-               OpName %4 "main"
-               OpName %28 "buf"
-               OpMemberName %28 0 "u1"
-               OpMemberName %28 1 "u2"
-               OpName %30 ""
-               OpName %85 "color"
-               OpMemberDecorate %28 0 Offset 0
-               OpMemberDecorate %28 1 Offset 4
-               OpDecorate %28 Block
-               OpDecorate %30 DescriptorSet 0
-               OpDecorate %30 Binding 0
-               OpDecorate %85 Location 0
-          %2 = OpTypeVoid
-          %3 = OpTypeFunction %2
-          %6 = OpTypeFloat 32
-          %7 = OpTypeVector %6 4
-         %10 = OpConstant %6 1
-         %11 = OpConstant %6 2
-         %12 = OpConstant %6 3
-         %13 = OpConstant %6 4
-         %14 = OpConstantComposite %7 %10 %11 %12 %13
-         %15 = OpTypeInt 32 1
-         %18 = OpConstant %15 0
-         %28 = OpTypeStruct %6 %6
-         %29 = OpTypePointer Uniform %28
-         %30 = OpVariable %29 Uniform
-         %31 = OpTypePointer Uniform %6
-         %35 = OpTypeBool
-         %39 = OpConstant %15 1
-         %84 = OpTypePointer Output %7
-         %85 = OpVariable %84 Output
-        %114 = OpConstant %15 8
-        %200 = OpTypeStruct %7 %15
-        %201 = OpTypeFunction %200 %15 %7 %15
-          %4 = OpFunction %2 None %3
-          %5 = OpLabel
-               OpBranch %22
-         %22 = OpLabel
-        %103 = OpPhi %15 %18 %5 %106 %43
-        %102 = OpPhi %7 %14 %5 %107 %43
-        %101 = OpPhi %15 %18 %5 %40 %43
-         %32 = OpAccessChain %31 %30 %18
-         %33 = OpLoad %6 %32
-         %34 = OpConvertFToS %15 %33
-         %36 = OpSLessThan %35 %101 %34
-               OpLoopMerge %24 %43 None
-               OpBranchConditional %36 %23 %24
-         %23 = OpLabel
-         %40 = OpIAdd %15 %101 %39
-               OpBranch %150
-        %150 = OpLabel
-        %204 = OpFunctionCall %200 %202 %103 %102 %40
-        %107 = OpCompositeExtract %7 %204 0
-        %106 = OpCompositeExtract %15 %204 1
-               OpBranch %43
-         %43 = OpLabel
-               OpBranch %22
-         %24 = OpLabel
-         %87 = OpCompositeExtract %6 %102 0
-         %91 = OpConvertSToF %6 %103
-         %92 = OpCompositeConstruct %7 %87 %11 %91 %10
-               OpStore %85 %92
-               OpReturn
-               OpFunctionEnd
-        %202 = OpFunction %200 None %201
-        %301 = OpFunctionParameter %15
-        %300 = OpFunctionParameter %7
-        %302 = OpFunctionParameter %15
-        %203 = OpLabel
-               OpBranch %41
-         %41 = OpLabel
-        %401 = OpPhi %7 %300 %203 %111 %65
-        %400 = OpPhi %15 %301 %203 %110 %65
-        %104 = OpPhi %15 %302 %203 %81 %65
-         %47 = OpAccessChain %31 %30 %39
-         %48 = OpLoad %6 %47
-         %49 = OpConvertFToS %15 %48
-         %50 = OpSLessThan %35 %104 %49
-               OpLoopMerge %1000 %65 None
-               OpBranchConditional %50 %42 %1000
-         %42 = OpLabel
-         %60 = OpIAdd %15 %400 %114
-         %63 = OpSGreaterThan %35 %104 %60
-               OpBranchConditional %63 %64 %65
-         %64 = OpLabel
-         %71 = OpCompositeExtract %6 %401 0
-         %72 = OpFAdd %6 %71 %11
-         %97 = OpCompositeInsert %7 %72 %401 0
-         %76 = OpCompositeExtract %6 %401 3
-         %77 = OpConvertFToS %15 %76
-         %79 = OpIAdd %15 %60 %77
-               OpBranch %65
-         %65 = OpLabel
-        %111 = OpPhi %7 %401 %42 %97 %64
-        %110 = OpPhi %15 %60 %42 %79 %64
-         %81 = OpIAdd %15 %104 %39
-               OpBranch %41
-       %1000 = OpLabel
-               OpBranch %1001
-       %1001 = OpLabel
-        %205 = OpCompositeConstruct %200 %401 %400
-               OpReturnValue %205
-               OpFunctionEnd
-  )";
-  ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+  TransformationOutlineFunction transformation_with_missing_output_id(
+      /*entry_block*/ 150,
+      /*exit_block*/ 1001,
+      /*new_function_struct_return_type_id*/ 200,
+      /*new_function_type_id*/ 201,
+      /*new_function_id*/ 202,
+      /*new_function_region_entry_block*/ 203,
+      /*new_caller_result_id*/ 204,
+      /*new_callee_result_id*/ 205,
+      /*input_id_to_fresh_id*/ {{102, 300}, {103, 301}, {40, 302}},
+      /*output_id_to_fresh_id*/ {{106, 400}});
+
+  TransformationOutlineFunction
+      transformation_with_missing_input_and_output_ids(
+          /*entry_block*/ 150,
+          /*exit_block*/ 1001,
+          /*new_function_struct_return_type_id*/ 200,
+          /*new_function_type_id*/ 201,
+          /*new_function_id*/ 202,
+          /*new_function_region_entry_block*/ 203,
+          /*new_caller_result_id*/ 204,
+          /*new_callee_result_id*/ 205,
+          /*input_id_to_fresh_id*/ {{102, 300}, {40, 302}},
+          /*output_id_to_fresh_id*/ {{106, 400}});
+
+  {
+    const auto context =
+        BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    FactManager fact_manager;
+    TransformationContext transformation_context(&fact_manager,
+                                                 validator_options);
+
+#ifndef NDEBUG
+    // We expect the following applicability checks to lead to assertion
+    // failures since the transformations are missing input or output ids, and
+    // the transformation context does not have a source of overflow ids.
+    ASSERT_DEATH(transformation_with_missing_input_id.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+    ASSERT_DEATH(transformation_with_missing_output_id.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+    ASSERT_DEATH(transformation_with_missing_input_and_output_ids.IsApplicable(
+                     context.get(), transformation_context),
+                 "Bad attempt to query whether overflow ids are available.");
+#endif
+
+    ASSERT_TRUE(
+        transformation.IsApplicable(context.get(), transformation_context));
+    transformation.Apply(context.get(), &transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    std::string variant_shader = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "main" %85
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %4 "main"
+                 OpName %28 "buf"
+                 OpMemberName %28 0 "u1"
+                 OpMemberName %28 1 "u2"
+                 OpName %30 ""
+                 OpName %85 "color"
+                 OpMemberDecorate %28 0 Offset 0
+                 OpMemberDecorate %28 1 Offset 4
+                 OpDecorate %28 Block
+                 OpDecorate %30 DescriptorSet 0
+                 OpDecorate %30 Binding 0
+                 OpDecorate %85 Location 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeVector %6 4
+           %10 = OpConstant %6 1
+           %11 = OpConstant %6 2
+           %12 = OpConstant %6 3
+           %13 = OpConstant %6 4
+           %14 = OpConstantComposite %7 %10 %11 %12 %13
+           %15 = OpTypeInt 32 1
+           %18 = OpConstant %15 0
+           %28 = OpTypeStruct %6 %6
+           %29 = OpTypePointer Uniform %28
+           %30 = OpVariable %29 Uniform
+           %31 = OpTypePointer Uniform %6
+           %35 = OpTypeBool
+           %39 = OpConstant %15 1
+           %84 = OpTypePointer Output %7
+           %85 = OpVariable %84 Output
+          %114 = OpConstant %15 8
+          %200 = OpTypeStruct %7 %15
+          %201 = OpTypeFunction %200 %15 %7 %15
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+                 OpBranch %22
+           %22 = OpLabel
+          %103 = OpPhi %15 %18 %5 %106 %43
+          %102 = OpPhi %7 %14 %5 %107 %43
+          %101 = OpPhi %15 %18 %5 %40 %43
+           %32 = OpAccessChain %31 %30 %18
+           %33 = OpLoad %6 %32
+           %34 = OpConvertFToS %15 %33
+           %36 = OpSLessThan %35 %101 %34
+                 OpLoopMerge %24 %43 None
+                 OpBranchConditional %36 %23 %24
+           %23 = OpLabel
+           %40 = OpIAdd %15 %101 %39
+                 OpBranch %150
+          %150 = OpLabel
+          %204 = OpFunctionCall %200 %202 %103 %102 %40
+          %107 = OpCompositeExtract %7 %204 0
+          %106 = OpCompositeExtract %15 %204 1
+                 OpBranch %43
+           %43 = OpLabel
+                 OpBranch %22
+           %24 = OpLabel
+           %87 = OpCompositeExtract %6 %102 0
+           %91 = OpConvertSToF %6 %103
+           %92 = OpCompositeConstruct %7 %87 %11 %91 %10
+                 OpStore %85 %92
+                 OpReturn
+                 OpFunctionEnd
+          %202 = OpFunction %200 None %201
+          %301 = OpFunctionParameter %15
+          %300 = OpFunctionParameter %7
+          %302 = OpFunctionParameter %15
+          %203 = OpLabel
+                 OpBranch %41
+           %41 = OpLabel
+          %401 = OpPhi %7 %300 %203 %111 %65
+          %400 = OpPhi %15 %301 %203 %110 %65
+          %104 = OpPhi %15 %302 %203 %81 %65
+           %47 = OpAccessChain %31 %30 %39
+           %48 = OpLoad %6 %47
+           %49 = OpConvertFToS %15 %48
+           %50 = OpSLessThan %35 %104 %49
+                 OpLoopMerge %1000 %65 None
+                 OpBranchConditional %50 %42 %1000
+           %42 = OpLabel
+           %60 = OpIAdd %15 %400 %114
+           %63 = OpSGreaterThan %35 %104 %60
+                 OpBranchConditional %63 %64 %65
+           %64 = OpLabel
+           %71 = OpCompositeExtract %6 %401 0
+           %72 = OpFAdd %6 %71 %11
+           %97 = OpCompositeInsert %7 %72 %401 0
+           %76 = OpCompositeExtract %6 %401 3
+           %77 = OpConvertFToS %15 %76
+           %79 = OpIAdd %15 %60 %77
+                 OpBranch %65
+           %65 = OpLabel
+          %111 = OpPhi %7 %401 %42 %97 %64
+          %110 = OpPhi %15 %60 %42 %79 %64
+           %81 = OpIAdd %15 %104 %39
+                 OpBranch %41
+         %1000 = OpLabel
+                 OpBranch %1001
+         %1001 = OpLabel
+          %205 = OpCompositeConstruct %200 %401 %400
+                 OpReturnValue %205
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+  }
+
+  {
+    const auto context =
+        BuildModule(env, consumer, reference_shader, kFuzzAssembleOption);
+    ASSERT_TRUE(IsValid(env, context.get()));
+    FactManager fact_manager;
+    TransformationContext new_transformation_context(
+        &fact_manager, validator_options,
+        MakeUnique<CounterOverflowIdSource>(2000));
+    ASSERT_TRUE(transformation_with_missing_input_id.IsApplicable(
+        context.get(), new_transformation_context));
+    ASSERT_TRUE(transformation_with_missing_output_id.IsApplicable(
+        context.get(), new_transformation_context));
+    ASSERT_TRUE(transformation_with_missing_input_and_output_ids.IsApplicable(
+        context.get(), new_transformation_context));
+    transformation_with_missing_input_and_output_ids.Apply(
+        context.get(), &new_transformation_context);
+    ASSERT_TRUE(IsValid(env, context.get()));
+
+    std::string variant_shader = R"(
+                 OpCapability Shader
+            %1 = OpExtInstImport "GLSL.std.450"
+                 OpMemoryModel Logical GLSL450
+                 OpEntryPoint Fragment %4 "main" %85
+                 OpExecutionMode %4 OriginUpperLeft
+                 OpSource ESSL 310
+                 OpName %4 "main"
+                 OpName %28 "buf"
+                 OpMemberName %28 0 "u1"
+                 OpMemberName %28 1 "u2"
+                 OpName %30 ""
+                 OpName %85 "color"
+                 OpMemberDecorate %28 0 Offset 0
+                 OpMemberDecorate %28 1 Offset 4
+                 OpDecorate %28 Block
+                 OpDecorate %30 DescriptorSet 0
+                 OpDecorate %30 Binding 0
+                 OpDecorate %85 Location 0
+            %2 = OpTypeVoid
+            %3 = OpTypeFunction %2
+            %6 = OpTypeFloat 32
+            %7 = OpTypeVector %6 4
+           %10 = OpConstant %6 1
+           %11 = OpConstant %6 2
+           %12 = OpConstant %6 3
+           %13 = OpConstant %6 4
+           %14 = OpConstantComposite %7 %10 %11 %12 %13
+           %15 = OpTypeInt 32 1
+           %18 = OpConstant %15 0
+           %28 = OpTypeStruct %6 %6
+           %29 = OpTypePointer Uniform %28
+           %30 = OpVariable %29 Uniform
+           %31 = OpTypePointer Uniform %6
+           %35 = OpTypeBool
+           %39 = OpConstant %15 1
+           %84 = OpTypePointer Output %7
+           %85 = OpVariable %84 Output
+          %114 = OpConstant %15 8
+          %200 = OpTypeStruct %7 %15
+          %201 = OpTypeFunction %200 %15 %7 %15
+            %4 = OpFunction %2 None %3
+            %5 = OpLabel
+                 OpBranch %22
+           %22 = OpLabel
+          %103 = OpPhi %15 %18 %5 %106 %43
+          %102 = OpPhi %7 %14 %5 %107 %43
+          %101 = OpPhi %15 %18 %5 %40 %43
+           %32 = OpAccessChain %31 %30 %18
+           %33 = OpLoad %6 %32
+           %34 = OpConvertFToS %15 %33
+           %36 = OpSLessThan %35 %101 %34
+                 OpLoopMerge %24 %43 None
+                 OpBranchConditional %36 %23 %24
+           %23 = OpLabel
+           %40 = OpIAdd %15 %101 %39
+                 OpBranch %150
+          %150 = OpLabel
+          %204 = OpFunctionCall %200 %202 %103 %102 %40
+          %107 = OpCompositeExtract %7 %204 0
+          %106 = OpCompositeExtract %15 %204 1
+                 OpBranch %43
+           %43 = OpLabel
+                 OpBranch %22
+           %24 = OpLabel
+           %87 = OpCompositeExtract %6 %102 0
+           %91 = OpConvertSToF %6 %103
+           %92 = OpCompositeConstruct %7 %87 %11 %91 %10
+                 OpStore %85 %92
+                 OpReturn
+                 OpFunctionEnd
+          %202 = OpFunction %200 None %201
+         %2000 = OpFunctionParameter %15
+          %300 = OpFunctionParameter %7
+          %302 = OpFunctionParameter %15
+          %203 = OpLabel
+                 OpBranch %41
+           %41 = OpLabel
+         %2001 = OpPhi %7 %300 %203 %111 %65
+          %400 = OpPhi %15 %2000 %203 %110 %65
+          %104 = OpPhi %15 %302 %203 %81 %65
+           %47 = OpAccessChain %31 %30 %39
+           %48 = OpLoad %6 %47
+           %49 = OpConvertFToS %15 %48
+           %50 = OpSLessThan %35 %104 %49
+                 OpLoopMerge %1000 %65 None
+                 OpBranchConditional %50 %42 %1000
+           %42 = OpLabel
+           %60 = OpIAdd %15 %400 %114
+           %63 = OpSGreaterThan %35 %104 %60
+                 OpBranchConditional %63 %64 %65
+           %64 = OpLabel
+           %71 = OpCompositeExtract %6 %2001 0
+           %72 = OpFAdd %6 %71 %11
+           %97 = OpCompositeInsert %7 %72 %2001 0
+           %76 = OpCompositeExtract %6 %2001 3
+           %77 = OpConvertFToS %15 %76
+           %79 = OpIAdd %15 %60 %77
+                 OpBranch %65
+           %65 = OpLabel
+          %111 = OpPhi %7 %2001 %42 %97 %64
+          %110 = OpPhi %15 %60 %42 %79 %64
+           %81 = OpIAdd %15 %104 %39
+                 OpBranch %41
+         %1000 = OpLabel
+                 OpBranch %1001
+         %1001 = OpLabel
+          %205 = OpCompositeConstruct %200 %2001 %400
+                 OpReturnValue %205
+                 OpFunctionEnd
+    )";
+    ASSERT_TRUE(IsEqual(env, variant_shader, context.get()));
+  }
 }
 
 TEST(TransformationOutlineFunctionTest, Miscellaneous2) {
diff --git a/tools/fuzz/fuzz.cpp b/tools/fuzz/fuzz.cpp
index 61064d1..551f3f4 100644
--- a/tools/fuzz/fuzz.cpp
+++ b/tools/fuzz/fuzz.cpp
@@ -428,7 +428,7 @@
 
   auto replay_result_status = replayer.Run(
       binary_in, initial_facts, transformation_sequence,
-      num_transformations_to_apply, binary_out, transformations_applied);
+      num_transformations_to_apply, 0, binary_out, transformations_applied);
   return !(replay_result_status !=
            spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
 }