1fd4e5da5Sopenharmony_ci// Copyright (c) 2019 Google LLC
2fd4e5da5Sopenharmony_ci//
3fd4e5da5Sopenharmony_ci// Licensed under the Apache License, Version 2.0 (the "License");
4fd4e5da5Sopenharmony_ci// you may not use this file except in compliance with the License.
5fd4e5da5Sopenharmony_ci// You may obtain a copy of the License at
6fd4e5da5Sopenharmony_ci//
7fd4e5da5Sopenharmony_ci//     http://www.apache.org/licenses/LICENSE-2.0
8fd4e5da5Sopenharmony_ci//
9fd4e5da5Sopenharmony_ci// Unless required by applicable law or agreed to in writing, software
10fd4e5da5Sopenharmony_ci// distributed under the License is distributed on an "AS IS" BASIS,
11fd4e5da5Sopenharmony_ci// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12fd4e5da5Sopenharmony_ci// See the License for the specific language governing permissions and
13fd4e5da5Sopenharmony_ci// limitations under the License.
14fd4e5da5Sopenharmony_ci
15fd4e5da5Sopenharmony_ci#include "source/fuzz/shrinker.h"
16fd4e5da5Sopenharmony_ci
17fd4e5da5Sopenharmony_ci#include <sstream>
18fd4e5da5Sopenharmony_ci
19fd4e5da5Sopenharmony_ci#include "source/fuzz/added_function_reducer.h"
20fd4e5da5Sopenharmony_ci#include "source/fuzz/pseudo_random_generator.h"
21fd4e5da5Sopenharmony_ci#include "source/fuzz/replayer.h"
22fd4e5da5Sopenharmony_ci#include "source/opt/build_module.h"
23fd4e5da5Sopenharmony_ci#include "source/opt/ir_context.h"
24fd4e5da5Sopenharmony_ci#include "source/spirv_fuzzer_options.h"
25fd4e5da5Sopenharmony_ci#include "source/util/make_unique.h"
26fd4e5da5Sopenharmony_ci
27fd4e5da5Sopenharmony_cinamespace spvtools {
28fd4e5da5Sopenharmony_cinamespace fuzz {
29fd4e5da5Sopenharmony_ci
30fd4e5da5Sopenharmony_cinamespace {
31fd4e5da5Sopenharmony_ci
32fd4e5da5Sopenharmony_ci// A helper to get the size of a protobuf transformation sequence in a less
33fd4e5da5Sopenharmony_ci// verbose manner.
34fd4e5da5Sopenharmony_ciuint32_t NumRemainingTransformations(
35fd4e5da5Sopenharmony_ci    const protobufs::TransformationSequence& transformation_sequence) {
36fd4e5da5Sopenharmony_ci  return static_cast<uint32_t>(transformation_sequence.transformation_size());
37fd4e5da5Sopenharmony_ci}
38fd4e5da5Sopenharmony_ci
39fd4e5da5Sopenharmony_ci// A helper to return a transformation sequence identical to |transformations|,
40fd4e5da5Sopenharmony_ci// except that a chunk of size |chunk_size| starting from |chunk_index| x
41fd4e5da5Sopenharmony_ci// |chunk_size| is removed (or as many transformations as available if the whole
42fd4e5da5Sopenharmony_ci// chunk is not).
43fd4e5da5Sopenharmony_ciprotobufs::TransformationSequence RemoveChunk(
44fd4e5da5Sopenharmony_ci    const protobufs::TransformationSequence& transformations,
45fd4e5da5Sopenharmony_ci    uint32_t chunk_index, uint32_t chunk_size) {
46fd4e5da5Sopenharmony_ci  uint32_t lower = chunk_index * chunk_size;
47fd4e5da5Sopenharmony_ci  uint32_t upper = std::min((chunk_index + 1) * chunk_size,
48fd4e5da5Sopenharmony_ci                            NumRemainingTransformations(transformations));
49fd4e5da5Sopenharmony_ci  assert(lower < upper);
50fd4e5da5Sopenharmony_ci  assert(upper <= NumRemainingTransformations(transformations));
51fd4e5da5Sopenharmony_ci  protobufs::TransformationSequence result;
52fd4e5da5Sopenharmony_ci  for (uint32_t j = 0; j < NumRemainingTransformations(transformations); j++) {
53fd4e5da5Sopenharmony_ci    if (j >= lower && j < upper) {
54fd4e5da5Sopenharmony_ci      continue;
55fd4e5da5Sopenharmony_ci    }
56fd4e5da5Sopenharmony_ci    protobufs::Transformation transformation =
57fd4e5da5Sopenharmony_ci        transformations.transformation()[j];
58fd4e5da5Sopenharmony_ci    *result.mutable_transformation()->Add() = transformation;
59fd4e5da5Sopenharmony_ci  }
60fd4e5da5Sopenharmony_ci  return result;
61fd4e5da5Sopenharmony_ci}
62fd4e5da5Sopenharmony_ci
63fd4e5da5Sopenharmony_ci}  // namespace
64fd4e5da5Sopenharmony_ci
65fd4e5da5Sopenharmony_ciShrinker::Shrinker(
66fd4e5da5Sopenharmony_ci    spv_target_env target_env, MessageConsumer consumer,
67fd4e5da5Sopenharmony_ci    const std::vector<uint32_t>& binary_in,
68fd4e5da5Sopenharmony_ci    const protobufs::FactSequence& initial_facts,
69fd4e5da5Sopenharmony_ci    const protobufs::TransformationSequence& transformation_sequence_in,
70fd4e5da5Sopenharmony_ci    const InterestingnessFunction& interestingness_function,
71fd4e5da5Sopenharmony_ci    uint32_t step_limit, bool validate_during_replay,
72fd4e5da5Sopenharmony_ci    spv_validator_options validator_options)
73fd4e5da5Sopenharmony_ci    : target_env_(target_env),
74fd4e5da5Sopenharmony_ci      consumer_(std::move(consumer)),
75fd4e5da5Sopenharmony_ci      binary_in_(binary_in),
76fd4e5da5Sopenharmony_ci      initial_facts_(initial_facts),
77fd4e5da5Sopenharmony_ci      transformation_sequence_in_(transformation_sequence_in),
78fd4e5da5Sopenharmony_ci      interestingness_function_(interestingness_function),
79fd4e5da5Sopenharmony_ci      step_limit_(step_limit),
80fd4e5da5Sopenharmony_ci      validate_during_replay_(validate_during_replay),
81fd4e5da5Sopenharmony_ci      validator_options_(validator_options) {}
82fd4e5da5Sopenharmony_ci
83fd4e5da5Sopenharmony_ciShrinker::~Shrinker() = default;
84fd4e5da5Sopenharmony_ci
85fd4e5da5Sopenharmony_ciShrinker::ShrinkerResult Shrinker::Run() {
86fd4e5da5Sopenharmony_ci  // Check compatibility between the library version being linked with and the
87fd4e5da5Sopenharmony_ci  // header files being used.
88fd4e5da5Sopenharmony_ci  GOOGLE_PROTOBUF_VERIFY_VERSION;
89fd4e5da5Sopenharmony_ci
90fd4e5da5Sopenharmony_ci  SpirvTools tools(target_env_);
91fd4e5da5Sopenharmony_ci  if (!tools.IsValid()) {
92fd4e5da5Sopenharmony_ci    consumer_(SPV_MSG_ERROR, nullptr, {},
93fd4e5da5Sopenharmony_ci              "Failed to create SPIRV-Tools interface; stopping.");
94fd4e5da5Sopenharmony_ci    return {Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface,
95fd4e5da5Sopenharmony_ci            std::vector<uint32_t>(), protobufs::TransformationSequence()};
96fd4e5da5Sopenharmony_ci  }
97fd4e5da5Sopenharmony_ci
98fd4e5da5Sopenharmony_ci  // Initial binary should be valid.
99fd4e5da5Sopenharmony_ci  if (!tools.Validate(&binary_in_[0], binary_in_.size(), validator_options_)) {
100fd4e5da5Sopenharmony_ci    consumer_(SPV_MSG_INFO, nullptr, {},
101fd4e5da5Sopenharmony_ci              "Initial binary is invalid; stopping.");
102fd4e5da5Sopenharmony_ci    return {Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid,
103fd4e5da5Sopenharmony_ci            std::vector<uint32_t>(), protobufs::TransformationSequence()};
104fd4e5da5Sopenharmony_ci  }
105fd4e5da5Sopenharmony_ci
106fd4e5da5Sopenharmony_ci  // Run a replay of the initial transformation sequence to check that it
107fd4e5da5Sopenharmony_ci  // succeeds.
108fd4e5da5Sopenharmony_ci  auto initial_replay_result =
109fd4e5da5Sopenharmony_ci      Replayer(target_env_, consumer_, binary_in_, initial_facts_,
110fd4e5da5Sopenharmony_ci               transformation_sequence_in_,
111fd4e5da5Sopenharmony_ci               static_cast<uint32_t>(
112fd4e5da5Sopenharmony_ci                   transformation_sequence_in_.transformation_size()),
113fd4e5da5Sopenharmony_ci               validate_during_replay_, validator_options_)
114fd4e5da5Sopenharmony_ci          .Run();
115fd4e5da5Sopenharmony_ci  if (initial_replay_result.status !=
116fd4e5da5Sopenharmony_ci      Replayer::ReplayerResultStatus::kComplete) {
117fd4e5da5Sopenharmony_ci    return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(),
118fd4e5da5Sopenharmony_ci            protobufs::TransformationSequence()};
119fd4e5da5Sopenharmony_ci  }
120fd4e5da5Sopenharmony_ci  // Get the binary that results from running these transformations, and the
121fd4e5da5Sopenharmony_ci  // subsequence of the initial transformations that actually apply (in
122fd4e5da5Sopenharmony_ci  // principle this could be a strict subsequence).
123fd4e5da5Sopenharmony_ci  std::vector<uint32_t> current_best_binary;
124fd4e5da5Sopenharmony_ci  initial_replay_result.transformed_module->module()->ToBinary(
125fd4e5da5Sopenharmony_ci      &current_best_binary, false);
126fd4e5da5Sopenharmony_ci  protobufs::TransformationSequence current_best_transformations =
127fd4e5da5Sopenharmony_ci      std::move(initial_replay_result.applied_transformations);
128fd4e5da5Sopenharmony_ci
129fd4e5da5Sopenharmony_ci  // Check that the binary produced by applying the initial transformations is
130fd4e5da5Sopenharmony_ci  // indeed interesting.
131fd4e5da5Sopenharmony_ci  if (!interestingness_function_(current_best_binary, 0)) {
132fd4e5da5Sopenharmony_ci    consumer_(SPV_MSG_INFO, nullptr, {},
133fd4e5da5Sopenharmony_ci              "Initial binary is not interesting; stopping.");
134fd4e5da5Sopenharmony_ci    return {ShrinkerResultStatus::kInitialBinaryNotInteresting,
135fd4e5da5Sopenharmony_ci            std::vector<uint32_t>(), protobufs::TransformationSequence()};
136fd4e5da5Sopenharmony_ci  }
137fd4e5da5Sopenharmony_ci
138fd4e5da5Sopenharmony_ci  uint32_t attempt = 0;  // Keeps track of the number of shrink attempts that
139fd4e5da5Sopenharmony_ci                         // have been tried, whether successful or not.
140fd4e5da5Sopenharmony_ci
141fd4e5da5Sopenharmony_ci  uint32_t chunk_size =
142fd4e5da5Sopenharmony_ci      std::max(1u, NumRemainingTransformations(current_best_transformations) /
143fd4e5da5Sopenharmony_ci                       2);  // The number of contiguous transformations that the
144fd4e5da5Sopenharmony_ci                            // shrinker will try to remove in one go; starts
145fd4e5da5Sopenharmony_ci                            // high and decreases during the shrinking process.
146fd4e5da5Sopenharmony_ci
147fd4e5da5Sopenharmony_ci  // Keep shrinking until we:
148fd4e5da5Sopenharmony_ci  // - reach the step limit,
149fd4e5da5Sopenharmony_ci  // - run out of transformations to remove, or
150fd4e5da5Sopenharmony_ci  // - cannot make the chunk size any smaller.
151fd4e5da5Sopenharmony_ci  while (attempt < step_limit_ &&
152fd4e5da5Sopenharmony_ci         !current_best_transformations.transformation().empty() &&
153fd4e5da5Sopenharmony_ci         chunk_size > 0) {
154fd4e5da5Sopenharmony_ci    bool progress_this_round =
155fd4e5da5Sopenharmony_ci        false;  // Used to decide whether to make the chunk size with which we
156fd4e5da5Sopenharmony_ci                // remove transformations smaller.  If we managed to remove at
157fd4e5da5Sopenharmony_ci                // least one chunk of transformations at a particular chunk
158fd4e5da5Sopenharmony_ci                // size, we set this flag so that we do not yet decrease the
159fd4e5da5Sopenharmony_ci                // chunk size.
160fd4e5da5Sopenharmony_ci
161fd4e5da5Sopenharmony_ci    assert(chunk_size <=
162fd4e5da5Sopenharmony_ci               NumRemainingTransformations(current_best_transformations) &&
163fd4e5da5Sopenharmony_ci           "Chunk size should never exceed the number of transformations that "
164fd4e5da5Sopenharmony_ci           "remain.");
165fd4e5da5Sopenharmony_ci
166fd4e5da5Sopenharmony_ci    // The number of chunks is the ceiling of (#remaining_transformations /
167fd4e5da5Sopenharmony_ci    // chunk_size).
168fd4e5da5Sopenharmony_ci    const uint32_t num_chunks =
169fd4e5da5Sopenharmony_ci        (NumRemainingTransformations(current_best_transformations) +
170fd4e5da5Sopenharmony_ci         chunk_size - 1) /
171fd4e5da5Sopenharmony_ci        chunk_size;
172fd4e5da5Sopenharmony_ci    assert(num_chunks >= 1 && "There should be at least one chunk.");
173fd4e5da5Sopenharmony_ci    assert(num_chunks * chunk_size >=
174fd4e5da5Sopenharmony_ci               NumRemainingTransformations(current_best_transformations) &&
175fd4e5da5Sopenharmony_ci           "All transformations should be in some chunk.");
176fd4e5da5Sopenharmony_ci
177fd4e5da5Sopenharmony_ci    // We go through the transformations in reverse, in chunks of size
178fd4e5da5Sopenharmony_ci    // |chunk_size|, using |chunk_index| to track which chunk to try removing
179fd4e5da5Sopenharmony_ci    // next.  The loop exits early if we reach the shrinking step limit.
180fd4e5da5Sopenharmony_ci    for (int chunk_index = num_chunks - 1;
181fd4e5da5Sopenharmony_ci         attempt < step_limit_ && chunk_index >= 0; chunk_index--) {
182fd4e5da5Sopenharmony_ci      // Remove a chunk of transformations according to the current index and
183fd4e5da5Sopenharmony_ci      // chunk size.
184fd4e5da5Sopenharmony_ci      auto transformations_with_chunk_removed =
185fd4e5da5Sopenharmony_ci          RemoveChunk(current_best_transformations,
186fd4e5da5Sopenharmony_ci                      static_cast<uint32_t>(chunk_index), chunk_size);
187fd4e5da5Sopenharmony_ci
188fd4e5da5Sopenharmony_ci      // Replay the smaller sequence of transformations to get a next binary and
189fd4e5da5Sopenharmony_ci      // transformation sequence. Note that the transformations arising from
190fd4e5da5Sopenharmony_ci      // replay might be even smaller than the transformations with the chunk
191fd4e5da5Sopenharmony_ci      // removed, because removing those transformations might make further
192fd4e5da5Sopenharmony_ci      // transformations inapplicable.
193fd4e5da5Sopenharmony_ci      auto replay_result =
194fd4e5da5Sopenharmony_ci          Replayer(
195fd4e5da5Sopenharmony_ci              target_env_, consumer_, binary_in_, initial_facts_,
196fd4e5da5Sopenharmony_ci              transformations_with_chunk_removed,
197fd4e5da5Sopenharmony_ci              static_cast<uint32_t>(
198fd4e5da5Sopenharmony_ci                  transformations_with_chunk_removed.transformation_size()),
199fd4e5da5Sopenharmony_ci              validate_during_replay_, validator_options_)
200fd4e5da5Sopenharmony_ci              .Run();
201fd4e5da5Sopenharmony_ci      if (replay_result.status != Replayer::ReplayerResultStatus::kComplete) {
202fd4e5da5Sopenharmony_ci        // Replay should not fail; if it does, we need to abort shrinking.
203fd4e5da5Sopenharmony_ci        return {ShrinkerResultStatus::kReplayFailed, std::vector<uint32_t>(),
204fd4e5da5Sopenharmony_ci                protobufs::TransformationSequence()};
205fd4e5da5Sopenharmony_ci      }
206fd4e5da5Sopenharmony_ci
207fd4e5da5Sopenharmony_ci      assert(
208fd4e5da5Sopenharmony_ci          NumRemainingTransformations(replay_result.applied_transformations) >=
209fd4e5da5Sopenharmony_ci              chunk_index * chunk_size &&
210fd4e5da5Sopenharmony_ci          "Removing this chunk of transformations should not have an effect "
211fd4e5da5Sopenharmony_ci          "on earlier chunks.");
212fd4e5da5Sopenharmony_ci
213fd4e5da5Sopenharmony_ci      std::vector<uint32_t> transformed_binary;
214fd4e5da5Sopenharmony_ci      replay_result.transformed_module->module()->ToBinary(&transformed_binary,
215fd4e5da5Sopenharmony_ci                                                           false);
216fd4e5da5Sopenharmony_ci      if (interestingness_function_(transformed_binary, attempt)) {
217fd4e5da5Sopenharmony_ci        // If the binary arising from the smaller transformation sequence is
218fd4e5da5Sopenharmony_ci        // interesting, this becomes our current best binary and transformation
219fd4e5da5Sopenharmony_ci        // sequence.
220fd4e5da5Sopenharmony_ci        current_best_binary = std::move(transformed_binary);
221fd4e5da5Sopenharmony_ci        current_best_transformations =
222fd4e5da5Sopenharmony_ci            std::move(replay_result.applied_transformations);
223fd4e5da5Sopenharmony_ci        progress_this_round = true;
224fd4e5da5Sopenharmony_ci      }
225fd4e5da5Sopenharmony_ci      // Either way, this was a shrink attempt, so increment our count of shrink
226fd4e5da5Sopenharmony_ci      // attempts.
227fd4e5da5Sopenharmony_ci      attempt++;
228fd4e5da5Sopenharmony_ci    }
229fd4e5da5Sopenharmony_ci    if (!progress_this_round) {
230fd4e5da5Sopenharmony_ci      // If we didn't manage to remove any chunks at this chunk size, try a
231fd4e5da5Sopenharmony_ci      // smaller chunk size.
232fd4e5da5Sopenharmony_ci      chunk_size /= 2;
233fd4e5da5Sopenharmony_ci    }
234fd4e5da5Sopenharmony_ci    // Decrease the chunk size until it becomes no larger than the number of
235fd4e5da5Sopenharmony_ci    // remaining transformations.
236fd4e5da5Sopenharmony_ci    while (chunk_size >
237fd4e5da5Sopenharmony_ci           NumRemainingTransformations(current_best_transformations)) {
238fd4e5da5Sopenharmony_ci      chunk_size /= 2;
239fd4e5da5Sopenharmony_ci    }
240fd4e5da5Sopenharmony_ci  }
241fd4e5da5Sopenharmony_ci
242fd4e5da5Sopenharmony_ci  // We now use spirv-reduce to minimise the functions associated with any
243fd4e5da5Sopenharmony_ci  // AddFunction transformations that remain.
244fd4e5da5Sopenharmony_ci  //
245fd4e5da5Sopenharmony_ci  // Consider every remaining transformation.
246fd4e5da5Sopenharmony_ci  for (uint32_t transformation_index = 0;
247fd4e5da5Sopenharmony_ci       attempt < step_limit_ &&
248fd4e5da5Sopenharmony_ci       transformation_index <
249fd4e5da5Sopenharmony_ci           static_cast<uint32_t>(
250fd4e5da5Sopenharmony_ci               current_best_transformations.transformation_size());
251fd4e5da5Sopenharmony_ci       transformation_index++) {
252fd4e5da5Sopenharmony_ci    // Skip all transformations apart from TransformationAddFunction.
253fd4e5da5Sopenharmony_ci    if (!current_best_transformations.transformation(transformation_index)
254fd4e5da5Sopenharmony_ci             .has_add_function()) {
255fd4e5da5Sopenharmony_ci      continue;
256fd4e5da5Sopenharmony_ci    }
257fd4e5da5Sopenharmony_ci    // Invoke spirv-reduce on the function encoded in this AddFunction
258fd4e5da5Sopenharmony_ci    // transformation.  The details of this are rather involved, and so are
259fd4e5da5Sopenharmony_ci    // encapsulated in a separate class.
260fd4e5da5Sopenharmony_ci    auto added_function_reducer_result =
261fd4e5da5Sopenharmony_ci        AddedFunctionReducer(target_env_, consumer_, binary_in_, initial_facts_,
262fd4e5da5Sopenharmony_ci                             current_best_transformations, transformation_index,
263fd4e5da5Sopenharmony_ci                             interestingness_function_, validate_during_replay_,
264fd4e5da5Sopenharmony_ci                             validator_options_, step_limit_, attempt)
265fd4e5da5Sopenharmony_ci            .Run();
266fd4e5da5Sopenharmony_ci    // Reducing the added function should succeed.  If it doesn't, we report
267fd4e5da5Sopenharmony_ci    // a shrinking error.
268fd4e5da5Sopenharmony_ci    if (added_function_reducer_result.status !=
269fd4e5da5Sopenharmony_ci        AddedFunctionReducer::AddedFunctionReducerResultStatus::kComplete) {
270fd4e5da5Sopenharmony_ci      return {ShrinkerResultStatus::kAddedFunctionReductionFailed,
271fd4e5da5Sopenharmony_ci              std::vector<uint32_t>(), protobufs::TransformationSequence()};
272fd4e5da5Sopenharmony_ci    }
273fd4e5da5Sopenharmony_ci    assert(current_best_transformations.transformation_size() ==
274fd4e5da5Sopenharmony_ci               added_function_reducer_result.applied_transformations
275fd4e5da5Sopenharmony_ci                   .transformation_size() &&
276fd4e5da5Sopenharmony_ci           "The number of transformations should not have changed.");
277fd4e5da5Sopenharmony_ci    current_best_binary =
278fd4e5da5Sopenharmony_ci        std::move(added_function_reducer_result.transformed_binary);
279fd4e5da5Sopenharmony_ci    current_best_transformations =
280fd4e5da5Sopenharmony_ci        std::move(added_function_reducer_result.applied_transformations);
281fd4e5da5Sopenharmony_ci    // The added function reducer reports how many reduction attempts
282fd4e5da5Sopenharmony_ci    // spirv-reduce took when reducing the function.  We regard each of these
283fd4e5da5Sopenharmony_ci    // as a shrinker attempt.
284fd4e5da5Sopenharmony_ci    attempt += added_function_reducer_result.num_reduction_attempts;
285fd4e5da5Sopenharmony_ci  }
286fd4e5da5Sopenharmony_ci
287fd4e5da5Sopenharmony_ci  // Indicate whether shrinking completed or was truncated due to reaching the
288fd4e5da5Sopenharmony_ci  // step limit.
289fd4e5da5Sopenharmony_ci  //
290fd4e5da5Sopenharmony_ci  // Either way, the output from the shrinker is the best binary we saw, and the
291fd4e5da5Sopenharmony_ci  // transformations that led to it.
292fd4e5da5Sopenharmony_ci  assert(attempt <= step_limit_);
293fd4e5da5Sopenharmony_ci  if (attempt == step_limit_) {
294fd4e5da5Sopenharmony_ci    std::stringstream strstream;
295fd4e5da5Sopenharmony_ci    strstream << "Shrinking did not complete; step limit " << step_limit_
296fd4e5da5Sopenharmony_ci              << " was reached.";
297fd4e5da5Sopenharmony_ci    consumer_(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str());
298fd4e5da5Sopenharmony_ci    return {Shrinker::ShrinkerResultStatus::kStepLimitReached,
299fd4e5da5Sopenharmony_ci            std::move(current_best_binary),
300fd4e5da5Sopenharmony_ci            std::move(current_best_transformations)};
301fd4e5da5Sopenharmony_ci  }
302fd4e5da5Sopenharmony_ci  return {Shrinker::ShrinkerResultStatus::kComplete,
303fd4e5da5Sopenharmony_ci          std::move(current_best_binary),
304fd4e5da5Sopenharmony_ci          std::move(current_best_transformations)};
305fd4e5da5Sopenharmony_ci}
306fd4e5da5Sopenharmony_ci
307fd4e5da5Sopenharmony_ciuint32_t Shrinker::GetIdBound(const std::vector<uint32_t>& binary) const {
308fd4e5da5Sopenharmony_ci  // Build the module from the input binary.
309fd4e5da5Sopenharmony_ci  std::unique_ptr<opt::IRContext> ir_context =
310fd4e5da5Sopenharmony_ci      BuildModule(target_env_, consumer_, binary.data(), binary.size());
311fd4e5da5Sopenharmony_ci  assert(ir_context && "Error building module.");
312fd4e5da5Sopenharmony_ci  return ir_context->module()->id_bound();
313fd4e5da5Sopenharmony_ci}
314fd4e5da5Sopenharmony_ci
315fd4e5da5Sopenharmony_ci}  // namespace fuzz
316fd4e5da5Sopenharmony_ci}  // namespace spvtools
317