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 ¤t_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