Add spirv-tools fuzzer
This change adds a new tint fuzzer that uses SPIRV-Tools to fuzz SPIR-V binaries.
The fuzzer works on a corpus of SPIR-V shaders. For each shader from the corpus it uses
one of `spirv-fuzz`, `spirv-reduce` or `spirv-opt` to mutate and then runs the shader through
the Tint compiler in two steps:
- Converts the mutated shader to WGSL.
- Converts WGSL to some target language specified in the CLI arguments.
The list of all supported CLI arguments and their description is in the cli.h file.
Change-Id: I95c0741b78ccc600dd9a73c371d520bdf7814352
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/41945
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Vasyl Teliman <vasniktel@gmail.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Alastair Donaldson <allydonaldson@googlemail.com>
diff --git a/.gitignore b/.gitignore
index 31c9126..319c459 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@
third_party/googletest
third_party/gpuweb-cts
third_party/llvm-build
+third_party/protobuf
third_party/spirv-headers
third_party/spirv-tools
tools/clang
diff --git a/AUTHORS b/AUTHORS
index b422353..a66d09e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -5,3 +5,4 @@
# of contributors, see the revision history in source control.
Google LLC
+Vasyl Teliman
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8eb4e45..61a7fff 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -49,6 +49,7 @@
option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON)
option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
option(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
+option(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF)
option(TINT_BUILD_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT})
option(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF)
@@ -68,6 +69,7 @@
message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
+message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
message(STATUS "Tint build tests: ${TINT_BUILD_TESTS}")
message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
@@ -77,12 +79,30 @@
message(STATUS "Using python3")
find_package(PythonInterp 3 REQUIRED)
+if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
+ message(STATUS "TINT_BUILD_SPIRV_TOOLS_FUZZER is ON - setting
+ TINT_BUILD_FUZZERS,
+ TINT_BUILD_SPV_READER,
+ TINT_BUILD_WGSL_READER,
+ TINT_BUILD_WGSL_WRITER,
+ TINT_BUILD_HLSL_WRITER,
+ TINT_BUILD_MSL_WRITER,
+ TINT_BUILD_SPV_WRITER to ON")
+ set(TINT_BUILD_FUZZERS ON)
+ set(TINT_BUILD_SPV_READER ON)
+ set(TINT_BUILD_WGSL_READER ON)
+ set(TINT_BUILD_WGSL_WRITER ON)
+ set(TINT_BUILD_HLSL_WRITER ON)
+ set(TINT_BUILD_MSL_WRITER ON)
+ set(TINT_BUILD_SPV_WRITER ON)
+endif()
+
# CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there.
# See https://gitlab.kitware.com/cmake/cmake/-/issues/18317
if (MSVC)
- if (CMAKE_CXX_FLAGS MATCHES "/W3")
- string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
- endif()
+ if (CMAKE_CXX_FLAGS MATCHES "/W3")
+ string(REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ endif()
endif()
if (${TINT_CHECK_CHROMIUM_STYLE})
diff --git a/DEPS b/DEPS
index 110c2b4..d3b3dcb 100644
--- a/DEPS
+++ b/DEPS
@@ -11,6 +11,7 @@
'clang_revision': 'eb5ab41f3801e2085208204fd71a490573d72dfd',
'googletest_revision': '5c8ca58edfb304b2dd5e6061f83387470826dd87',
'gpuweb_cts_revision': '177a4faf0a7ce6f8c64b42a715c634e363912a74',
+ 'protobuf_revision': 'fde7cf7358ec7cd69e8db9be4f1fa6a5c431386a',
'spirv_headers_revision': 'f5417a4b6633c3217c9a1bc2f0c70b1454975ba7',
'spirv_tools_revision': 'ecdd9a3e6bd384bf51d096b507291faa10f14685',
'testing_revision': '2691851e49de541c3fe42fa8692ddcdee938162f',
@@ -42,6 +43,9 @@
'third_party/googletest': Var('chromium_git') + Var('github') +
'/google/googletest.git@' + Var('googletest_revision'),
+
+ 'third_party/protobuf': Var('chromium_git') + Var('github') +
+ '/protocolbuffers/protobuf.git@' + Var('protobuf_revision'),
}
hooks = [
diff --git a/Doxyfile b/Doxyfile
index 028d0ef..2e1c7aa 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -786,7 +786,8 @@
# Note: If this tag is empty the current directory is searched.
INPUT = CODE_OF_CONDUCT.md \
- src
+ src \
+ fuzzers/tint_spirv_tools_fuzzer
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
diff --git a/fuzzers/CMakeLists.txt b/fuzzers/CMakeLists.txt
index 34e927e..8588b45 100644
--- a/fuzzers/CMakeLists.txt
+++ b/fuzzers/CMakeLists.txt
@@ -76,3 +76,7 @@
if (${TINT_BUILD_WGSL_READER} AND ${TINT_BUILD_WGSL_WRITER})
add_tint_fuzzer(tint_ast_clone_fuzzer)
endif()
+
+if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
+ add_subdirectory(tint_spirv_tools_fuzzer)
+endif()
diff --git a/fuzzers/tint_common_fuzzer.cc b/fuzzers/tint_common_fuzzer.cc
index 6c92306..7d5879c 100644
--- a/fuzzers/tint_common_fuzzer.cc
+++ b/fuzzers/tint_common_fuzzer.cc
@@ -21,6 +21,7 @@
#include <vector>
#include "src/ast/module.h"
+#include "src/diagnostic/formatter.h"
#include "src/program.h"
namespace tint {
@@ -191,6 +192,7 @@
}
if (!program.IsValid()) {
+ errors_ = diag::Formatter().format(program.Diagnostics());
return 0;
}
@@ -199,58 +201,68 @@
auto entry_points = inspector.GetEntryPoints();
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
for (auto& ep : entry_points) {
auto remapped_name = inspector.GetRemappedNameForEntryPoint(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto constant_ids = inspector.GetConstantIDs();
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto uniform_bindings =
inspector.GetUniformBufferResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto storage_bindings =
inspector.GetStorageBufferResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto readonly_bindings =
inspector.GetReadOnlyStorageBufferResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto sampler_bindings = inspector.GetSamplerResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto comparison_sampler_bindings =
inspector.GetComparisonSamplerResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto sampled_texture_bindings =
inspector.GetSampledTextureResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
auto multisampled_texture_bindings =
inspector.GetMultisampledTextureResourceBindings(ep.name);
if (inspector.has_error()) {
+ errors_ = inspector.error();
return 0;
}
}
@@ -272,39 +284,44 @@
program = std::move(out.program);
}
- std::unique_ptr<writer::Writer> writer;
-
switch (output_) {
case OutputFormat::kWGSL:
#if TINT_BUILD_WGSL_WRITER
- writer = std::make_unique<writer::wgsl::Generator>(&program);
+ writer_ = std::make_unique<writer::wgsl::Generator>(&program);
#endif // TINT_BUILD_WGSL_WRITER
break;
case OutputFormat::kSpv:
#if TINT_BUILD_SPV_WRITER
- writer = std::make_unique<writer::spirv::Generator>(&program);
+ writer_ = std::make_unique<writer::spirv::Generator>(&program);
#endif // TINT_BUILD_SPV_WRITER
break;
case OutputFormat::kHLSL:
#if TINT_BUILD_HLSL_WRITER
- writer = std::make_unique<writer::hlsl::Generator>(&program);
+ writer_ = std::make_unique<writer::hlsl::Generator>(&program);
#endif // TINT_BUILD_HLSL_WRITER
break;
case OutputFormat::kMSL:
#if TINT_BUILD_MSL_WRITER
- writer = std::make_unique<writer::msl::Generator>(&program);
+ writer_ = std::make_unique<writer::msl::Generator>(&program);
#endif // TINT_BUILD_MSL_WRITER
break;
case OutputFormat::kNone:
break;
}
- if (writer) {
- writer->Generate();
+ if (writer_) {
+ if (!writer_->Generate()) {
+ errors_ = writer_->error();
+ return 0;
+ }
}
return 0;
}
+const writer::Writer* CommonFuzzer::GetWriter() const {
+ return writer_.get();
+}
+
} // namespace fuzzers
} // namespace tint
diff --git a/fuzzers/tint_common_fuzzer.h b/fuzzers/tint_common_fuzzer.h
index 372df35..9201850 100644
--- a/fuzzers/tint_common_fuzzer.h
+++ b/fuzzers/tint_common_fuzzer.h
@@ -16,6 +16,7 @@
#define FUZZERS_TINT_COMMON_FUZZER_H_
#include <cstring>
+#include <memory>
#include <string>
#include <utility>
#include <vector>
@@ -108,12 +109,20 @@
int Run(const uint8_t* data, size_t size);
+ const writer::Writer* GetWriter() const;
+
+ const std::string& GetErrors() const { return errors_; }
+
+ bool HasErrors() const { return !errors_.empty(); }
+
private:
InputFormat input_;
OutputFormat output_;
+ std::unique_ptr<writer::Writer> writer_;
transform::Manager* transform_manager_;
transform::DataMap transform_inputs_;
bool inspector_enabled_;
+ std::string errors_;
};
} // namespace fuzzers
diff --git a/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt b/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
new file mode 100644
index 0000000..92790c1
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/CMakeLists.txt
@@ -0,0 +1,81 @@
+# Copyright 2021 The Tint Authors.
+#
+# 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.
+
+set(FUZZER_SOURCES
+ cli.cc
+ fuzzer.cc
+ mutator.cc
+ mutator_cache.cc
+ spirv_fuzz_mutator.cc
+ spirv_opt_mutator.cc
+ spirv_reduce_mutator.cc
+ util.cc)
+
+set(FUZZER_SOURCES ${FUZZER_SOURCES}
+ cli.h
+ mutator.h
+ mutator_cache.h
+ spirv_fuzz_mutator.h
+ spirv_opt_mutator.h
+ spirv_reduce_mutator.h
+ util.h)
+
+set(FUZZER_SOURCES ${FUZZER_SOURCES}
+ ../tint_common_fuzzer.h
+ ../tint_common_fuzzer.cc)
+
+function(configure_spirv_tools_fuzzer_target NAME SOURCES)
+ add_executable(${NAME} ${SOURCES})
+ target_link_libraries(${NAME} SPIRV-Tools SPIRV-Tools-opt SPIRV-Tools-fuzz SPIRV-Tools-reduce)
+ tint_default_compile_options(${NAME})
+ target_compile_options(${NAME} PRIVATE
+ -Wno-missing-prototypes
+ -Wno-zero-as-null-pointer-constant
+ -Wno-reserved-id-macro
+ -Wno-sign-conversion
+ -Wno-extra-semi-stmt
+ -Wno-inconsistent-missing-destructor-override
+ -Wno-newline-eof
+ -Wno-old-style-cast
+ -Wno-weak-vtables
+ -Wno-undef)
+ target_include_directories(${NAME} PRIVATE
+ ${spirv-tools_SOURCE_DIR}
+ ${spirv-tools_BINARY_DIR})
+endfunction()
+
+configure_spirv_tools_fuzzer_target(tint_spirv_tools_fuzzer "${FUZZER_SOURCES}")
+target_compile_definitions(tint_spirv_tools_fuzzer PUBLIC CUSTOM_MUTATOR)
+target_compile_definitions(tint_spirv_tools_fuzzer PRIVATE TARGET_FUZZER)
+target_link_libraries(tint_spirv_tools_fuzzer libtint-fuzz)
+
+set(DEBUGGER_SOURCES
+ cli.cc
+ mutator.cc
+ mutator_debugger.cc
+ spirv_fuzz_mutator.cc
+ spirv_opt_mutator.cc
+ spirv_reduce_mutator.cc
+ util.cc)
+
+set(DEBUGGER_SOURCES ${DEBUGGER_SOURCES}
+ cli.h
+ mutator.h
+ spirv_fuzz_mutator.h
+ spirv_opt_mutator.h
+ spirv_reduce_mutator.h
+ util.h)
+
+configure_spirv_tools_fuzzer_target(tint_spirv_tools_mutator_debugger "${DEBUGGER_SOURCES}")
+target_compile_definitions(tint_spirv_tools_mutator_debugger PRIVATE TARGET_DEBUGGER)
diff --git a/fuzzers/tint_spirv_tools_fuzzer/cli.cc b/fuzzers/tint_spirv_tools_fuzzer/cli.cc
new file mode 100644
index 0000000..cfb6633
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/cli.cc
@@ -0,0 +1,467 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/cli.h"
+
+#include <fstream>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "source/opt/build_module.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace {
+
+const char* const kMutatorParameters = R"(
+Mutators' parameters:
+
+ --donors=
+ A path to the text file with a list of paths to the
+ SPIR-V donor files. Check out the doc for the spirv-fuzz
+ to learn more about donor binaries. Donors are not used
+ by default.
+
+ --enable_all_fuzzer_passes=
+ Whether to use all fuzzer passes or a randomly selected subset
+ of them. This must be one of `true` or `false` (without `).
+ By default it's `false`.
+
+ --enable_all_reduce_passes=
+ Whether to use all reduction passes or a randomly selected subset
+ of them. This must be one of `true` or `false` (without `).
+ By default it's `false`.
+
+ --opt_batch_size=
+ The maximum number of spirv-opt optimizations that
+ will be applied in a single mutation session (i.e.
+ a call to LLVMFuzzerCustomMutator). This must fit in
+ uint32_t. By default it's 6.
+
+ --reduction_batch_size=
+ The maximum number of spirv-reduce reductions that
+ will be applied in a single mutation session (i.e.
+ a call to LLVMFuzzerCustomMutator). This must fit in
+ uint32_t. By default it's 3.
+
+ --repeated_pass_strategy=
+ The strategy that will be used to recommend the next fuzzer
+ pass. This must be one of `simple`, `looped` or `random`
+ (without `). By default it's `simple`. Check out the doc for
+ spirv-fuzz to learn more.
+
+ --transformation_batch_size=
+ The maximum number of spirv-fuzz transformations
+ that will be applied during a single mutation
+ session (i.e. a call to LLVMFuzzerCustomMutator).
+ This must fit in uint32_t. By default it's 3.
+
+ --validate_after_each_fuzzer_pass=
+ Whether to validate SPIR-V binary after each fuzzer pass.
+ This must be one of `true` or `false` (without `).
+ By default it's `true`. Switch this to `false` if you experience
+ bad performance.
+
+ --validate_after_each_opt_pass=
+ Whether to validate SPIR-V binary after each optimization pass.
+ This must be one of `true` or `false` (without `).
+ By default it's `true`. Switch this to `false` if you experience
+ bad performance.
+
+ --validate_after_each_reduce_pass=
+ Whether to validate SPIR-V binary after each reduction pass.
+ This must be one of `true` or `false` (without `).
+ By default it's `true`. Switch this to `false` if you experience
+ bad performance.
+)";
+
+const char* const kFuzzerHelpMessage = R"(
+This fuzzer uses SPIR-V binaries to fuzz the Tint compiler. It uses SPIRV-Tools
+to mutate those binaries. The fuzzer works on a corpus of SPIR-V shaders.
+For each shader from the corpus it uses one of `spirv-fuzz`, `spirv-reduce` or
+`spirv-opt` to mutate it and then runs the shader through the Tint compiler in
+two steps:
+- Converts the mutated shader to WGSL.
+- Converts WGSL to some target language specified in the CLI arguments.
+
+Below is a list of all supported parameters for this fuzzer. You may want to
+run it with -help=1 to check out libfuzzer parameters.
+
+Fuzzer parameters:
+
+ --error_dir
+ The directory that will be used to output invalid SPIR-V
+ binaries to. This is especially useful during debugging
+ mutators. The directory must have the following subdirectories:
+ - spv/ - will be used to output errors, produced during
+ the conversion from the SPIR-V to WGSL.
+ - wgsl/ - will be used to output errors, produced during
+ the conversion from the WGSL to `--fuzzing_target`.
+ - mutator/ - will be used to output errors, produced by
+ the mutators.
+ By default invalid files are not printed out.
+
+ --fuzzing_target
+ The type of backend to target during fuzzing. This must
+ be one or a combination of `wgsl`, `spv`, `msl` or `hlsl`
+ (without `) separated by commas. By default it's
+ `wgsl,spv,msl,hlsl`.
+
+ --help
+ Show this message. Note that there is also a -help=1
+ parameter that will display libfuzzer's help message.
+
+ --mutator_cache_size=
+ The maximum size of the cache that stores
+ mutation sessions. This must fit in uint32_t.
+ By default it's 20.
+
+ --mutator_type=
+ Determines types of the mutators to run. This must be one or
+ a combination of `fuzz`, `opt`, `reduce` (without `) separated by
+ comma. If a combination is specified, each element in the
+ combination will have an equal chance of mutating a SPIR-V
+ binary during a mutation session (i.e. if no mutator exists
+ for that binary in the mutator cache). By default, the
+ parameter's value is `fuzz,opt,reduce`.
+)";
+
+const char* const kMutatorDebuggerHelpMessage = R"(
+This tool is used to debug *mutators*. It uses CLI arguments similar to the
+ones used by the fuzzer. To debug some mutator you just need to specify the
+mutator type, the seed and the path to the SPIR-V binary that triggered the
+error. This tool will run the mutator on the binary until the error is
+produced or the mutator returns `kLimitReached`.
+
+Note that this is different from debugging the fuzzer by specifying input
+files to test. The difference is that the latter will not execute any
+mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
+tool is useful when one of the SPIRV-Tools mutators crashes or produces an
+invalid binary in LLVMFuzzerCustomMutator.
+
+Debugger parameters:
+
+ --help
+ Show this message.
+
+ --mutator_type=
+ Determines the type of the mutator to debug. This must be
+ one of `fuzz`, `reduce` or `opt` (without `). This parameter
+ is REQUIRED.
+
+ --original_binary=
+ The path to the SPIR-V binary that the faulty mutator was
+ initialized with. This will be dumped on errors by the fuzzer
+ if `--error_dir` is specified. This parameter is REQUIRED.
+
+ --seed=
+ The seed for the random number generator that was used to
+ initialize the mutator. This value is usually printed to
+ the console when the mutator produces an invalid binary.
+ It is also dumped into the log file if `--error_dir` is
+ specified. This must fit in uint32_t. This parameter is
+ REQUIRED.
+)";
+
+void PrintHelpMessage(const char* help_message) {
+ std::cout << help_message << std::endl << kMutatorParameters << std::endl;
+}
+
+[[noreturn]] void InvalidParameter(const char* help_message,
+ const char* param) {
+ std::cout << "Invalid value for " << param << std::endl;
+ PrintHelpMessage(help_message);
+ exit(1);
+}
+
+bool ParseUint32(const char* param, uint32_t* out) {
+ auto value = strtoul(param, nullptr, 10);
+ if (value > std::numeric_limits<uint32_t>::max()) {
+ return false;
+ }
+ *out = static_cast<uint32_t>(value);
+ return true;
+}
+
+std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> ParseDonors(
+ const char* file_name) {
+ std::ifstream fin(file_name);
+ if (!fin) {
+ std::cout << "Can't open donors list file: " << file_name << std::endl;
+ exit(1);
+ }
+
+ std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> result;
+ for (std::string donor_file_name; fin >> donor_file_name;) {
+ if (!std::ifstream(donor_file_name)) {
+ std::cout << "Can't open donor file: " << donor_file_name << std::endl;
+ exit(1);
+ }
+
+ result.emplace_back([donor_file_name] {
+ std::vector<uint32_t> binary;
+ if (!util::ReadBinary(donor_file_name, &binary)) {
+ std::cout << "Failed to read donor from: " << donor_file_name
+ << std::endl;
+ exit(1);
+ }
+ return spvtools::BuildModule(
+ kDefaultTargetEnv, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+ binary.data(), binary.size());
+ });
+ }
+
+ return result;
+}
+
+bool ParseRepeatedPassStrategy(const char* param,
+ spvtools::fuzz::RepeatedPassStrategy* out) {
+ if (!strcmp(param, "simple")) {
+ *out = spvtools::fuzz::RepeatedPassStrategy::kSimple;
+ } else if (!strcmp(param, "looped")) {
+ *out = spvtools::fuzz::RepeatedPassStrategy::kLoopedWithRecommendations;
+ } else if (!strcmp(param, "random")) {
+ *out = spvtools::fuzz::RepeatedPassStrategy::kRandomWithRecommendations;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool ParseBool(const char* param, bool* out) {
+ if (!strcmp(param, "true")) {
+ *out = true;
+ } else if (!strcmp(param, "false")) {
+ *out = false;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool ParseMutatorType(const char* param, MutatorType* out) {
+ if (!strcmp(param, "fuzz")) {
+ *out = MutatorType::kFuzz;
+ } else if (!strcmp(param, "opt")) {
+ *out = MutatorType::kOpt;
+ } else if (!strcmp(param, "reduce")) {
+ *out = MutatorType::kReduce;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool ParseFuzzingTarget(const char* param, FuzzingTarget* out) {
+ if (!strcmp(param, "wgsl")) {
+ *out = FuzzingTarget::kWgsl;
+ } else if (!strcmp(param, "spv")) {
+ *out = FuzzingTarget::kSpv;
+ } else if (!strcmp(param, "msl")) {
+ *out = FuzzingTarget::kMsl;
+ } else if (!strcmp(param, "hlsl")) {
+ *out = FuzzingTarget::kHlsl;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+bool HasPrefix(const char* str, const char* prefix) {
+ return strncmp(str, prefix, strlen(prefix)) == 0;
+}
+
+void ParseMutatorCliParam(const char* param,
+ const char* help_message,
+ MutatorCliParams* out) {
+ if (HasPrefix(param, "--transformation_batch_size=")) {
+ if (!ParseUint32(param + sizeof("--transformation_batch_size=") - 1,
+ &out->transformation_batch_size)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--reduction_batch_size=")) {
+ if (!ParseUint32(param + sizeof("--reduction_batch_size=") - 1,
+ &out->reduction_batch_size)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--opt_batch_size=")) {
+ if (!ParseUint32(param + sizeof("--opt_batch_size=") - 1,
+ &out->opt_batch_size)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--donors=")) {
+ out->donors = ParseDonors(param + sizeof("--donors=") - 1);
+ } else if (HasPrefix(param, "--repeated_pass_strategy=")) {
+ if (!ParseRepeatedPassStrategy(
+ param + sizeof("--repeated_pass_strategy=") - 1,
+ &out->repeated_pass_strategy)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--enable_all_fuzzer_passes=")) {
+ if (!ParseBool(param + sizeof("--enable_all_fuzzer_passes=") - 1,
+ &out->enable_all_fuzzer_passes)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--enable_all_reduce_passes=")) {
+ if (!ParseBool(param + sizeof("--enable_all_reduce_passes=") - 1,
+ &out->enable_all_reduce_passes)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--validate_after_each_opt_pass=")) {
+ if (!ParseBool(param + sizeof("--validate_after_each_opt_pass=") - 1,
+ &out->validate_after_each_opt_pass)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--validate_after_each_fuzzer_pass=")) {
+ if (!ParseBool(param + sizeof("--validate_after_each_fuzzer_pass=") - 1,
+ &out->validate_after_each_fuzzer_pass)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--validate_after_each_reduce_pass=")) {
+ if (!ParseBool(param + sizeof("--validate_after_each_reduce_pass=") - 1,
+ &out->validate_after_each_reduce_pass)) {
+ InvalidParameter(help_message, param);
+ }
+ }
+}
+
+} // namespace
+
+FuzzerCliParams ParseFuzzerCliParams(int argc, const char* const* argv) {
+ FuzzerCliParams cli_params;
+ const auto* help_message = kFuzzerHelpMessage;
+ auto help = false;
+
+ for (int i = 0; i < argc; ++i) {
+ auto param = argv[i];
+ ParseMutatorCliParam(param, help_message, &cli_params.mutator_params);
+
+ if (HasPrefix(param, "--mutator_cache_size=")) {
+ if (!ParseUint32(param + sizeof("--mutator_cache_size=") - 1,
+ &cli_params.mutator_cache_size)) {
+ InvalidParameter(help_message, param);
+ }
+ } else if (HasPrefix(param, "--mutator_type=")) {
+ auto result = MutatorType::kNone;
+
+ std::stringstream ss(param + sizeof("--mutator_type=") - 1);
+ for (std::string value; std::getline(ss, value, ',');) {
+ auto out = MutatorType::kNone;
+ if (!ParseMutatorType(value.c_str(), &out)) {
+ InvalidParameter(help_message, param);
+ }
+ result = result | out;
+ }
+
+ if (result == MutatorType::kNone) {
+ InvalidParameter(help_message, param);
+ }
+
+ cli_params.mutator_type = result;
+ } else if (HasPrefix(param, "--fuzzing_target=")) {
+ auto result = FuzzingTarget::kNone;
+
+ std::stringstream ss(param + sizeof("--fuzzing_target=") - 1);
+ for (std::string value; std::getline(ss, value, ',');) {
+ auto tmp = FuzzingTarget::kNone;
+ if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
+ InvalidParameter(help_message, param);
+ }
+ result = result | tmp;
+ }
+
+ if (result == FuzzingTarget::kNone) {
+ InvalidParameter(help_message, param);
+ }
+
+ cli_params.fuzzing_target = result;
+ } else if (HasPrefix(param, "--error_dir=")) {
+ cli_params.error_dir = param + sizeof("--error_dir=") - 1;
+ } else if (!strcmp(param, "--help")) {
+ help = true;
+ }
+ }
+
+ if (help) {
+ PrintHelpMessage(help_message);
+ exit(0);
+ }
+
+ return cli_params;
+}
+
+MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(
+ int argc,
+ const char* const* argv) {
+ MutatorDebuggerCliParams cli_params;
+ bool seed_param_present = false;
+ bool original_binary_param_present = false;
+ bool mutator_type_param_present = false;
+ const auto* help_message = kMutatorDebuggerHelpMessage;
+ auto help = false;
+
+ for (int i = 0; i < argc; ++i) {
+ auto param = argv[i];
+ ParseMutatorCliParam(param, help_message, &cli_params.mutator_params);
+
+ if (HasPrefix(param, "--mutator_type=")) {
+ if (!ParseMutatorType(param + sizeof("--mutator_type=") - 1,
+ &cli_params.mutator_type)) {
+ InvalidParameter(help_message, param);
+ }
+ mutator_type_param_present = true;
+ } else if (HasPrefix(param, "--original_binary=")) {
+ if (!util::ReadBinary(param + sizeof("--original_binary=") - 1,
+ &cli_params.original_binary)) {
+ InvalidParameter(help_message, param);
+ }
+ original_binary_param_present = true;
+ } else if (HasPrefix(param, "--seed=")) {
+ if (!ParseUint32(param + sizeof("--seed=") - 1, &cli_params.seed)) {
+ InvalidParameter(help_message, param);
+ }
+ seed_param_present = true;
+ } else if (!strcmp(param, "--help")) {
+ help = true;
+ }
+ }
+
+ if (help) {
+ PrintHelpMessage(help_message);
+ exit(0);
+ }
+
+ std::pair<bool, const char*> required_params[] = {
+ {seed_param_present, "--seed"},
+ {original_binary_param_present, "--original_binary"},
+ {mutator_type_param_present, "--mutator_type"}};
+
+ for (auto required_param : required_params) {
+ if (!required_param.first) {
+ std::cout << required_param.second << " is missing" << std::endl;
+ exit(1);
+ }
+ }
+
+ return cli_params;
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/cli.h b/fuzzers/tint_spirv_tools_fuzzer/cli.h
new file mode 100644
index 0000000..7913a6b
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/cli.h
@@ -0,0 +1,124 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
+
+#include <string>
+#include <vector>
+
+#include "source/fuzz/fuzzer.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Default SPIR-V environment that will be used during fuzzing.
+const auto kDefaultTargetEnv = SPV_ENV_VULKAN_1_1;
+
+/// The type of the mutator to run.
+enum class MutatorType {
+ kNone = 0,
+ kFuzz = 1 << 0,
+ kReduce = 1 << 1,
+ kOpt = 1 << 2,
+ kAll = kFuzz | kReduce | kOpt
+};
+
+inline MutatorType operator|(MutatorType a, MutatorType b) {
+ return static_cast<MutatorType>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline MutatorType operator&(MutatorType a, MutatorType b) {
+ return static_cast<MutatorType>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// Shading language to target during fuzzing.
+enum class FuzzingTarget {
+ kNone = 0,
+ kHlsl = 1 << 0,
+ kMsl = 1 << 1,
+ kSpv = 1 << 2,
+ kWgsl = 1 << 3,
+ kAll = kHlsl | kMsl | kSpv | kWgsl
+};
+
+inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
+ return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
+}
+
+inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
+ return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
+}
+
+/// These parameters are accepted by various mutators and thus they are accepted
+/// by both the fuzzer and the mutator debugger.
+struct MutatorCliParams {
+ spv_target_env target_env = kDefaultTargetEnv;
+ uint32_t transformation_batch_size = 3;
+ uint32_t reduction_batch_size = 3;
+ uint32_t opt_batch_size = 6;
+ std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier> donors = {};
+ spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy =
+ spvtools::fuzz::RepeatedPassStrategy::kSimple;
+ bool enable_all_fuzzer_passes = false;
+ bool enable_all_reduce_passes = false;
+ bool validate_after_each_opt_pass = true;
+ bool validate_after_each_fuzzer_pass = true;
+ bool validate_after_each_reduce_pass = true;
+};
+
+/// Parameters specific to the fuzzer.
+struct FuzzerCliParams {
+ uint32_t mutator_cache_size = 20;
+ MutatorType mutator_type = MutatorType::kAll;
+ FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
+ std::string error_dir;
+ MutatorCliParams mutator_params;
+};
+
+/// Parameters specific to the mutator debugger.
+struct MutatorDebuggerCliParams {
+ MutatorType mutator_type = MutatorType::kNone;
+ uint32_t seed = 0;
+ std::vector<uint32_t> original_binary;
+ MutatorCliParams mutator_params;
+};
+
+/// Parses CLI parameters for the fuzzer. This function exits with an error code
+/// and a message is printed to the console if some parameter has invalid
+/// format. You can pass `--help` to check out all available parameters.
+///
+/// @param argc - the number of parameters (identical to the `argc` in `main`
+/// function).
+/// @param argv - array of C strings of parameters.
+/// @return the parsed parameters.
+FuzzerCliParams ParseFuzzerCliParams(int argc, const char* const* argv);
+
+/// Parses CLI parameters for the mutator debugger. This function exits with an
+/// error code and a message is printed to the console if some parameter has
+/// invalid format. You can pass `--help` to check out all available parameters.
+///
+/// @param argc - the number of parameters (identical to the `argc` in `main`
+/// function).
+/// @param argv - array of C strings of parameters.
+/// @return the parsed parameters.
+MutatorDebuggerCliParams ParseMutatorDebuggerCliParams(int argc,
+ const char* const* argv);
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_CLI_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc b/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
new file mode 100644
index 0000000..30acdf4
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/fuzzer.cc
@@ -0,0 +1,216 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 <iostream>
+#include <memory>
+#include <random>
+#include <string>
+#include <vector>
+
+#include "fuzzers/tint_common_fuzzer.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace {
+
+struct Context {
+ const FuzzerCliParams params;
+ std::unique_ptr<MutatorCache> mutator_cache;
+};
+
+Context* context = nullptr;
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+ auto params = ParseFuzzerCliParams(*argc, *argv);
+ auto mutator_cache =
+ params.mutator_cache_size
+ ? std::make_unique<MutatorCache>(params.mutator_cache_size)
+ : nullptr;
+ context = new Context{std::move(params), std::move(mutator_cache)};
+ return 0;
+}
+
+std::unique_ptr<Mutator> CreateMutator(const std::vector<uint32_t>& binary,
+ unsigned seed) {
+ std::vector<MutatorType> types;
+ types.reserve(3);
+
+ // Determine which mutator we will be using for `binary` at random.
+ auto cli_mutator_type = context->params.mutator_type;
+ if ((MutatorType::kFuzz & cli_mutator_type) == MutatorType::kFuzz) {
+ types.push_back(MutatorType::kFuzz);
+ }
+ if ((MutatorType::kReduce & cli_mutator_type) == MutatorType::kReduce) {
+ types.push_back(MutatorType::kReduce);
+ }
+ if ((MutatorType::kOpt & cli_mutator_type) == MutatorType::kOpt) {
+ types.push_back(MutatorType::kOpt);
+ }
+
+ assert(!types.empty() && "At least one mutator type must be specified");
+ std::mt19937 rng(seed);
+ auto mutator_type =
+ types[std::uniform_int_distribution<size_t>(0, types.size() - 1)(rng)];
+
+ const auto& mutator_params = context->params.mutator_params;
+ switch (mutator_type) {
+ case MutatorType::kFuzz:
+ return std::make_unique<SpirvFuzzMutator>(
+ mutator_params.target_env, binary, seed, mutator_params.donors,
+ mutator_params.enable_all_fuzzer_passes,
+ mutator_params.repeated_pass_strategy,
+ mutator_params.validate_after_each_fuzzer_pass,
+ mutator_params.transformation_batch_size);
+ case MutatorType::kReduce:
+ return std::make_unique<SpirvReduceMutator>(
+ mutator_params.target_env, binary, seed,
+ mutator_params.reduction_batch_size,
+ mutator_params.enable_all_reduce_passes,
+ mutator_params.validate_after_each_reduce_pass);
+ case MutatorType::kOpt:
+ return std::make_unique<SpirvOptMutator>(
+ mutator_params.target_env, seed, binary,
+ mutator_params.validate_after_each_opt_pass,
+ mutator_params.opt_batch_size);
+ default:
+ assert(false && "All mutator types must be handled above");
+ return nullptr;
+ }
+}
+
+extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
+ size_t size,
+ size_t max_size,
+ unsigned seed) {
+ std::vector<uint32_t> binary(size / sizeof(uint32_t));
+ std::memcpy(binary.data(), data, size);
+
+ MutatorCache dummy_cache(1);
+ auto* mutator_cache = context->mutator_cache.get();
+ if (!mutator_cache) {
+ // Use a dummy cache if the user has decided not to use a real cache.
+ // The dummy cache will be destroyed when we return from this function but
+ // it will save us from writing all the `if (mutator_cache)` below.
+ mutator_cache = &dummy_cache;
+ }
+
+ if (!mutator_cache->Get(binary)) {
+ // Assign a mutator to the binary if it doesn't have one yet.
+ mutator_cache->Put(binary, CreateMutator(binary, seed));
+ }
+
+ auto* mutator = mutator_cache->Get(binary);
+ assert(mutator && "Mutator must be present in the cache");
+
+ auto result = mutator->Mutate();
+
+ if (result.GetStatus() == Mutator::Status::kInvalid) {
+ // The binary is invalid - log the error and remove the mutator from the
+ // cache.
+ util::LogMutatorError(*mutator, context->params.error_dir);
+ mutator_cache->Remove(binary);
+ return 0;
+ }
+
+ if (!result.IsChanged()) {
+ // The mutator didn't change the binary this time. This could be due to the
+ // fact that we've reached the number of mutations we can apply (e.g. the
+ // number of transformations in spirv-fuzz) or the mutator was just unlucky.
+ // Either way, there is no harm in destroying mutator and maybe trying again
+ // later (i.e. if libfuzzer decides to do so).
+ mutator_cache->Remove(binary);
+ return 0;
+ }
+
+ // At this point the binary is valid and was changed by the mutator.
+
+ auto mutated = mutator->GetBinary();
+ auto mutated_bytes_size = mutated.size() * sizeof(uint32_t);
+ if (mutated_bytes_size > max_size) {
+ // The binary is too big. It's unlikely that we'll reduce its size by
+ // applying the mutator one more time.
+ mutator_cache->Remove(binary);
+ return 0;
+ }
+
+ if (result.GetStatus() == Mutator::Status::kComplete) {
+ // Reassign the mutator to the mutated binary in the cache so that we can
+ // access later.
+ mutator_cache->Put(mutated, mutator_cache->Remove(binary));
+ } else {
+ // If the binary is valid and was changed but is not `kComplete`, then the
+ // mutator has reached some limit on the number of mutations.
+ mutator_cache->Remove(binary);
+ }
+
+ std::memcpy(data, mutated.data(), mutated_bytes_size);
+ return mutated_bytes_size;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (size == 0) {
+ return 0;
+ }
+
+ CommonFuzzer spv_to_wgsl(InputFormat::kSpv, OutputFormat::kWGSL);
+ spv_to_wgsl.EnableInspector();
+ spv_to_wgsl.Run(data, size);
+ if (spv_to_wgsl.HasErrors()) {
+ util::LogSpvError(spv_to_wgsl.GetErrors(), data, size,
+ context->params.error_dir);
+ return 0;
+ }
+
+ const auto* writer =
+ static_cast<const writer::wgsl::Generator*>(spv_to_wgsl.GetWriter());
+
+ assert(writer && writer->error().empty() &&
+ "Errors should have already been handled");
+
+ auto wgsl = writer->result();
+
+ std::pair<FuzzingTarget, OutputFormat> targets[] = {
+ {FuzzingTarget::kHlsl, OutputFormat::kHLSL},
+ {FuzzingTarget::kMsl, OutputFormat::kMSL},
+ {FuzzingTarget::kSpv, OutputFormat::kSpv},
+ {FuzzingTarget::kWgsl, OutputFormat::kWGSL}};
+
+ for (auto target : targets) {
+ if ((target.first & context->params.fuzzing_target) != target.first) {
+ continue;
+ }
+
+ CommonFuzzer fuzzer(InputFormat::kWGSL, target.second);
+ fuzzer.EnableInspector();
+ fuzzer.Run(reinterpret_cast<const uint8_t*>(wgsl.data()), wgsl.size());
+ if (fuzzer.HasErrors()) {
+ util::LogWgslError(fuzzer.GetErrors(), data, size, wgsl, target.second,
+ context->params.error_dir);
+ }
+ }
+
+ return 0;
+}
+
+} // namespace
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
new file mode 100644
index 0000000..76cae68
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/mutator.cc
@@ -0,0 +1,34 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+// We need to define constructor here so that vtable is produced in this
+// translation unit (see -Wweak-vtables clang flag).
+Mutator::~Mutator() = default;
+
+Mutator::Result::Result(Status status, bool is_changed)
+ : status_(status), is_changed_(is_changed) {
+ assert((is_changed || status == Status::kStuck ||
+ status == Status::kLimitReached) &&
+ "Returning invalid result state");
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator.h b/fuzzers/tint_spirv_tools_fuzzer/mutator.h
new file mode 100644
index 0000000..ee3eecf
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/mutator.h
@@ -0,0 +1,108 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
+
+#include <cassert>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// This is an interface that is used to define custom mutators based on the
+/// SPIR-V tools.
+class Mutator {
+ public:
+ /// The status of the mutation.
+ enum class Status {
+ /// Binary is valid, the limit is not reached - can mutate further.
+ kComplete,
+
+ /// The binary is valid, the limit of mutations has been reached -
+ /// can't mutate further.
+ kLimitReached,
+
+ /// The binary is valid, the limit is not reached but the mutator has spent
+ /// too much time without mutating anything - better to restart to make sure
+ /// we can make any progress.
+ kStuck,
+
+ /// The binary is invalid - this is likely a bug in the mutator - must
+ /// abort.
+ kInvalid
+ };
+
+ /// Represents the result of the mutation. The following states are possible:
+ /// - if `IsChanged() == false`, then `GetStatus()` can be either
+ /// `kLimitReached` or `kStuck`.
+ /// - otherwise, any value of `Status` is possible.
+ class Result {
+ public:
+ /// Constructor.
+ /// @param status - the status of the mutation.
+ /// @param is_changed - whether the module was changed during mutation.
+ Result(Status status, bool is_changed);
+
+ /// @return the status of the mutation.
+ Status GetStatus() const { return status_; }
+
+ /// @return whether the module was changed during mutation.
+ bool IsChanged() const { return is_changed_; }
+
+ private:
+ Status status_;
+ bool is_changed_;
+ };
+
+ /// Virtual destructor.
+ virtual ~Mutator();
+
+ /// Causes the mutator to apply a mutation. This method can be called
+ /// multiple times as long as the previous call didn't return
+ /// `Status::kInvalid`.
+ ///
+ /// @return the status of the mutation (e.g. success, error etc) and whether
+ /// the binary was changed during mutation.
+ virtual Result Mutate() = 0;
+
+ /// Returns the mutated binary. The returned binary is guaranteed to be valid
+ /// iff the previous call to the `Mutate` method returned didn't return
+ /// `Status::kInvalid`.
+ ///
+ /// @return the mutated SPIR-V binary. It might be identical to the original
+ /// binary if `Result::IsChanged` returns `false`.
+ virtual std::vector<uint32_t> GetBinary() const = 0;
+
+ /// Returns errors, produced by the mutator.
+ ///
+ /// @param path - the directory to which the errors are printed to. No files
+ /// are created if the `path` is nullptr.
+ /// @param count - the number of the error. Files for this error will be
+ /// prefixed with `count`.
+ virtual void LogErrors(const std::string* path, uint32_t count) const = 0;
+
+ /// @return errors encountered during the mutation. The returned string is
+ /// if there were no errors during mutation.
+ virtual std::string GetErrors() const = 0;
+};
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
new file mode 100644
index 0000000..03849f7
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.cc
@@ -0,0 +1,78 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+MutatorCache::MutatorCache(size_t max_size)
+ : map_(), entries_(), max_size_(max_size) {
+ assert(max_size && "`max_size` may not be 0");
+}
+
+MutatorCache::Value::pointer MutatorCache::Get(const Key& key) {
+ auto it = map_.find(key);
+ if (it == map_.end()) {
+ return nullptr;
+ }
+ UpdateUsage(it);
+ return entries_.front().second.get();
+}
+
+void MutatorCache::Put(const Key& key, Value value) {
+ assert(value && "Mutator cache can't have nullptr unique_ptr");
+ auto it = map_.find(key);
+ if (it != map_.end()) {
+ it->second->second = std::move(value);
+ UpdateUsage(it);
+ } else {
+ if (map_.size() == max_size_) {
+ Remove(*entries_.back().first);
+ }
+
+ entries_.emplace_front(nullptr, std::move(value));
+ auto pair = map_.emplace(key, entries_.begin());
+ assert(pair.second && "The key must be unique");
+ entries_.front().first = &pair.first->first;
+ }
+}
+
+MutatorCache::Value MutatorCache::Remove(const Key& key) {
+ auto it = map_.find(key);
+ if (it == map_.end()) {
+ return nullptr;
+ }
+ auto result = std::move(it->second->second);
+ entries_.erase(it->second);
+ map_.erase(it);
+ return result;
+}
+
+size_t MutatorCache::KeyHash::operator()(
+ const std::vector<uint32_t>& vec) const {
+ return std::hash<std::u32string>()({vec.begin(), vec.end()});
+}
+
+void MutatorCache::UpdateUsage(Map::iterator it) {
+ auto entry = std::move(*it->second);
+ entries_.erase(it->second);
+ entries_.push_front(std::move(entry));
+ it->second = entries_.begin();
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
new file mode 100644
index 0000000..90dd5ce
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/mutator_cache.h
@@ -0,0 +1,99 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
+
+#include <cassert>
+#include <list>
+#include <memory>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Implementation of a fixed size LRU cache. That is, when the number of
+/// elements reaches a certain threshold, the element that wasn't used for the
+/// longest period of time is removed from the cache when a new element is
+/// inserted. All operations have amortized constant time complexity.
+class MutatorCache {
+ public:
+ /// SPIR-V binary that is being mutated.
+ using Key = std::vector<uint32_t>;
+
+ /// Mutator that is used to mutate the `Key`.
+ using Value = std::unique_ptr<Mutator>;
+
+ /// Constructor.
+ /// @param max_size - the maximum number of elements the cache can store. May
+ /// not be equal to 0.
+ explicit MutatorCache(size_t max_size);
+
+ /// Retrieves a pointer to a value, associated with a given `key`.
+ ///
+ /// If the key is present in the cache, its usage is updated and the
+ /// (non-null) pointer to the value is returned. Otherwise, `nullptr` is
+ /// returned.
+ ///
+ /// @param key - may not exist in this cache.
+ /// @return non-`nullptr` pointer to a value if `key` exists in the cache.
+ /// @return `nullptr` if `key` doesn't exist in this cache.
+ Value::pointer Get(const Key& key);
+
+ /// Inserts a `key`-`value` pair into the cache.
+ ///
+ /// If the `key` is already present, the `value` replaces the old value and
+ /// the usage of `key` is updated. If the `key` is not present, then:
+ /// - if the number of elements in the cache is equal to `max_size`, the
+ /// key-value pair, where the usage of the key wasn't updated for the
+ /// longest period of time, is removed from the cache.
+ /// - a new `key`-`value` pair is inserted into the cache.
+ ///
+ /// @param key - a key.
+ /// @param value - may not be a `nullptr`.
+ void Put(const Key& key, Value value);
+
+ /// Removes `key` and an associated value from the cache.
+ ///
+ /// @param key - a key.
+ /// @return a non-`nullptr` pointer to the removed value, associated with
+ /// `key`.
+ /// @return `nullptr` if `key` is not present in the cache.
+ Value Remove(const Key& key);
+
+ private:
+ struct KeyHash {
+ size_t operator()(const std::vector<uint32_t>& vec) const;
+ };
+
+ using Entry = std::pair<const Key*, Value>;
+ using Map = std::unordered_map<Key, std::list<Entry>::iterator, KeyHash>;
+
+ void UpdateUsage(Map::iterator it);
+
+ Map map_;
+ std::list<Entry> entries_;
+ const size_t max_size_;
+};
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_MUTATOR_CACHE_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc b/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
new file mode 100644
index 0000000..26f0419
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/mutator_debugger.cc
@@ -0,0 +1,84 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 <memory>
+#include <string>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/cli.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+/// This tool is used to debug *mutators*. It uses CLI arguments similar to the
+/// ones used by the fuzzer. To debug some mutator you just need to specify the
+/// mutator type, the seed and the path to the SPIR-V binary that triggered the
+/// error. This tool will run the mutator on the binary until the error is
+/// produced or the mutator returns `kLimitReached`.
+///
+/// Note that this is different from debugging the fuzzer by specifying input
+/// files to test. The difference is that the latter will not execute any
+/// mutator (it will only run the LLVMFuzzerTestOneInput function) whereas this
+/// tool is useful when one of the spirv-tools mutators crashes or produces an
+/// invalid binary in LLVMFuzzerCustomMutator.
+int main(int argc, const char** argv) {
+ auto params =
+ tint::fuzzers::spvtools_fuzzer::ParseMutatorDebuggerCliParams(argc, argv);
+
+ std::unique_ptr<tint::fuzzers::spvtools_fuzzer::Mutator> mutator;
+ const auto& mutator_params = params.mutator_params;
+ switch (params.mutator_type) {
+ case tint::fuzzers::spvtools_fuzzer::MutatorType::kFuzz:
+ mutator =
+ std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvFuzzMutator>(
+ mutator_params.target_env, params.original_binary, params.seed,
+ mutator_params.donors, mutator_params.enable_all_fuzzer_passes,
+ mutator_params.repeated_pass_strategy,
+ mutator_params.validate_after_each_fuzzer_pass,
+ mutator_params.transformation_batch_size);
+ break;
+ case tint::fuzzers::spvtools_fuzzer::MutatorType::kReduce:
+ mutator =
+ std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvReduceMutator>(
+ mutator_params.target_env, params.original_binary, params.seed,
+ mutator_params.reduction_batch_size,
+ mutator_params.enable_all_reduce_passes,
+ mutator_params.validate_after_each_reduce_pass);
+ break;
+ case tint::fuzzers::spvtools_fuzzer::MutatorType::kOpt:
+ mutator =
+ std::make_unique<tint::fuzzers::spvtools_fuzzer::SpirvOptMutator>(
+ mutator_params.target_env, params.seed, params.original_binary,
+ mutator_params.validate_after_each_opt_pass,
+ mutator_params.opt_batch_size);
+ break;
+ default:
+ assert(false && "All mutator types must've been handled");
+ return 1;
+ }
+
+ while (true) {
+ auto result = mutator->Mutate();
+ if (result.GetStatus() ==
+ tint::fuzzers::spvtools_fuzzer::Mutator::Status::kInvalid) {
+ std::cerr << mutator->GetErrors() << std::endl;
+ return 0;
+ }
+ if (result.GetStatus() ==
+ tint::fuzzers::spvtools_fuzzer::Mutator::Status::kLimitReached) {
+ break;
+ }
+ }
+}
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
new file mode 100644
index 0000000..dca10f1
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.cc
@@ -0,0 +1,127 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h"
+
+#include <fstream>
+#include <utility>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "source/opt/build_module.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvFuzzMutator::SpirvFuzzMutator(
+ spv_target_env target_env,
+ std::vector<uint32_t> binary,
+ unsigned seed,
+ const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
+ bool enable_all_passes,
+ spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
+ bool validate_after_each_pass,
+ uint32_t transformation_batch_size)
+ : transformation_batch_size_(transformation_batch_size),
+ errors_(std::make_unique<std::stringstream>()),
+ fuzzer_(nullptr),
+ validator_options_(),
+ original_binary_(std::move(binary)),
+ seed_(seed) {
+ auto ir_context = spvtools::BuildModule(
+ target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+ original_binary_.data(), original_binary_.size());
+ assert(ir_context && "|binary| is invalid");
+
+ auto transformation_context =
+ std::make_unique<spvtools::fuzz::TransformationContext>(
+ std::make_unique<spvtools::fuzz::FactManager>(ir_context.get()),
+ validator_options_);
+
+ auto fuzzer_context = std::make_unique<spvtools::fuzz::FuzzerContext>(
+ std::make_unique<spvtools::fuzz::PseudoRandomGenerator>(seed),
+ spvtools::fuzz::FuzzerContext::GetMinFreshId(ir_context.get()), false);
+ fuzzer_ = std::make_unique<spvtools::fuzz::Fuzzer>(
+ std::move(ir_context), std::move(transformation_context),
+ std::move(fuzzer_context), util::GetBufferMessageConsumer(errors_.get()),
+ donors, enable_all_passes, repeated_pass_strategy,
+ validate_after_each_pass, validator_options_);
+}
+
+Mutator::Result SpirvFuzzMutator::Mutate() {
+ // The assertion will fail in |fuzzer_->Run| if the previous fuzzing led to
+ // invalid module.
+ auto result = fuzzer_->Run(transformation_batch_size_);
+ switch (result.status) {
+ case spvtools::fuzz::Fuzzer::Status::kComplete:
+ return {Mutator::Status::kComplete, result.is_changed};
+ case spvtools::fuzz::Fuzzer::Status::kModuleTooBig:
+ case spvtools::fuzz::Fuzzer::Status::kTransformationLimitReached:
+ return {Mutator::Status::kLimitReached, result.is_changed};
+ case spvtools::fuzz::Fuzzer::Status::kFuzzerStuck:
+ return {Mutator::Status::kStuck, result.is_changed};
+ case spvtools::fuzz::Fuzzer::Status::kFuzzerPassLedToInvalidModule:
+ return {Mutator::Status::kInvalid, result.is_changed};
+ }
+}
+
+std::vector<uint32_t> SpirvFuzzMutator::GetBinary() const {
+ std::vector<uint32_t> result;
+ fuzzer_->GetIRContext()->module()->ToBinary(&result, true);
+ return result;
+}
+
+std::string SpirvFuzzMutator::GetErrors() const {
+ return errors_->str();
+}
+
+void SpirvFuzzMutator::LogErrors(const std::string* path,
+ uint32_t count) const {
+ auto message = GetErrors();
+ std::cout << count << " | SpirvFuzzMutator (seed: " << seed_ << ")"
+ << std::endl;
+ std::cout << message << std::endl;
+
+ if (path) {
+ auto prefix = *path + std::to_string(count);
+
+ // Write errors to file.
+ std::ofstream(prefix + ".fuzzer.log") << "seed: " << seed_ << std::endl
+ << message << std::endl;
+
+ // Write the invalid SPIR-V binary.
+ util::WriteBinary(prefix + ".fuzzer.invalid.spv", GetBinary());
+
+ // Write the original SPIR-V binary.
+ util::WriteBinary(prefix + ".fuzzer.original.spv", original_binary_);
+
+ // Write transformations.
+ google::protobuf::util::JsonOptions options;
+ options.add_whitespace = true;
+ std::string json;
+ google::protobuf::util::MessageToJsonString(
+ fuzzer_->GetTransformationSequence(), &json, options);
+ std::ofstream(prefix + ".fuzzer.transformations.json") << json << std::endl;
+
+ std::ofstream binary_transformations(
+ prefix + ".fuzzer.transformations.binary",
+ std::ios::binary | std::ios::out);
+ fuzzer_->GetTransformationSequence().SerializeToOstream(
+ &binary_transformations);
+ }
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
new file mode 100644
index 0000000..79aa07d
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_fuzz_mutator.h
@@ -0,0 +1,95 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "source/fuzz/fuzzer.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/pseudo_random_generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// The mutator that uses spirv-fuzz to mutate SPIR-V.
+///
+/// The initial `binary` must be valid according to `target_env`. All other
+/// parameters (except for the `seed` which just initializes the RNG) are from
+/// the `spvtools::fuzz::Fuzzer` class.
+class SpirvFuzzMutator : public Mutator {
+ public:
+ /// Constructor.
+ /// @param target_env - the target environment for the `binary`.
+ /// @param binary - the SPIR-V binary. Must be valid.
+ /// @param seed - seed for the RNG.
+ /// @param donors - vector of donor suppliers.
+ /// @param enable_all_passes - whether to use all fuzzer passes.
+ /// @param repeated_pass_strategy - the strategy to use when selecting the
+ /// next fuzzer pass.
+ /// @param validate_after_each_pass - whether to validate the binary after
+ /// each fuzzer pass.
+ /// @param transformation_batch_size - the maximum number of transformations
+ /// that will be applied during a single call to `Mutate`. It it's equal
+ /// to 0 then we apply as much transformations as we can until the
+ /// threshold in the spvtools::fuzz::Fuzzer is reached (see the doc for
+ /// that class for more info).
+ SpirvFuzzMutator(
+ spv_target_env target_env,
+ std::vector<uint32_t> binary,
+ uint32_t seed,
+ const std::vector<spvtools::fuzz::fuzzerutil::ModuleSupplier>& donors,
+ bool enable_all_passes,
+ spvtools::fuzz::RepeatedPassStrategy repeated_pass_strategy,
+ bool validate_after_each_pass,
+ uint32_t transformation_batch_size);
+
+ Result Mutate() override;
+ std::vector<uint32_t> GetBinary() const override;
+ void LogErrors(const std::string* path, uint32_t count) const override;
+ std::string GetErrors() const override;
+
+ private:
+ // The number of transformations that will be applied during a single call to
+ // the `Mutate` method. Is this only a lower bound since transformations are
+ // applied in batches by fuzzer passes (see docs for the
+ // `spvtools::fuzz::Fuzzer` for more info).
+ const uint32_t transformation_batch_size_;
+
+ // The errors produced by the `spvtools::fuzz::Fuzzer`.
+ std::unique_ptr<std::stringstream> errors_;
+ std::unique_ptr<spvtools::fuzz::Fuzzer> fuzzer_;
+ spvtools::ValidatorOptions validator_options_;
+
+ // The following fields are useful for debugging.
+
+ // The binary that the mutator is constructed with.
+ const std::vector<uint32_t> original_binary_;
+
+ // The seed that the mutator is constructed with.
+ const uint32_t seed_;
+};
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_FUZZ_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
new file mode 100644
index 0000000..360f1d9
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.cc
@@ -0,0 +1,159 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h"
+
+#include <fstream>
+#include <iostream>
+#include <unordered_set>
+#include <utility>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "spirv-tools/optimizer.hpp"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvOptMutator::SpirvOptMutator(spv_target_env target_env,
+ uint32_t seed,
+ std::vector<uint32_t> binary,
+ bool validate_after_each_opt,
+ uint32_t opt_batch_size)
+ : num_executions_(0),
+ is_valid_(true),
+ target_env_(target_env),
+ original_binary_(std::move(binary)),
+ seed_(seed),
+ opt_passes_({"--combine-access-chains",
+ "--loop-unroll",
+ "--merge-blocks",
+ "--cfg-cleanup",
+ "--eliminate-dead-functions",
+ "--merge-return",
+ "--wrap-opkill",
+ "--eliminate-dead-code-aggressive",
+ "--if-conversion",
+ "--eliminate-local-single-store",
+ "--eliminate-local-single-block",
+ "--eliminate-dead-branches",
+ "--scalar-replacement=0",
+ "--eliminate-dead-inserts",
+ "--eliminate-dead-members",
+ "--simplify-instructions",
+ "--private-to-local",
+ "--ssa-rewrite",
+ "--ccp",
+ "--reduce-load-size",
+ "--vector-dce",
+ "--scalar-replacement=100",
+ "--inline-entry-points-exhaustive",
+ "--redundancy-elimination",
+ "--convert-local-access-chains",
+ "--copy-propagate-arrays",
+ "--fix-storage-class"}),
+ optimized_binary_(),
+ validate_after_each_opt_(validate_after_each_opt),
+ opt_batch_size_(opt_batch_size),
+ rng_(seed) {
+ assert(spvtools::SpirvTools(target_env).Validate(original_binary_) &&
+ "Initial binary is invalid");
+ assert(!opt_passes_.empty() && "Must be at least one pass");
+}
+
+SpirvOptMutator::Result SpirvOptMutator::Mutate() {
+ assert(is_valid_ && "The optimizer is not longer valid");
+
+ const uint32_t kMaxNumExecutions = 100;
+ const uint32_t kMaxNumStuck = 10;
+
+ if (num_executions_ == kMaxNumExecutions) {
+ // We've applied this mutator many times already. Indicate to the user that
+ // it might be better to try a different mutator.
+ return {Status::kLimitReached, false};
+ }
+
+ num_executions_++;
+
+ // Get the input binary. If this is the first time we run this mutator, use
+ // the `original_binary_`. Otherwise, one of the following will be true:
+ // - the `optimized_binary_` is not empty.
+ // - the previous call to the `Mutate` method returned `kStuck`.
+ auto binary = num_executions_ == 1 ? original_binary_ : optimized_binary_;
+ optimized_binary_.clear();
+
+ assert(!binary.empty() && "Can't run the optimizer on an empty binary");
+
+ // Number of times spirv-opt wasn't able to produce any new result.
+ uint32_t num_stuck = 0;
+ do {
+ // Randomly select `opt_batch_size` optimization passes. If `opt_batch_size`
+ // is equal to 0, we will use the number of passes equal to the number of
+ // all available passes.
+ auto num_of_passes = opt_batch_size_ ? opt_batch_size_ : opt_passes_.size();
+ std::vector<std::string> passes;
+
+ while (passes.size() < num_of_passes) {
+ auto idx = std::uniform_int_distribution<size_t>(
+ 0, opt_passes_.size() - 1)(rng_);
+ passes.push_back(opt_passes_[idx]);
+ }
+
+ // Run the `binary` into the `optimized_binary_`.
+ spvtools::Optimizer optimizer(target_env_);
+ optimizer.SetMessageConsumer(util::GetBufferMessageConsumer(&errors_));
+ optimizer.SetValidateAfterAll(validate_after_each_opt_);
+ optimizer.RegisterPassesFromFlags(passes);
+ if (!optimizer.Run(binary.data(), binary.size(), &optimized_binary_)) {
+ is_valid_ = false;
+ return {Status::kInvalid, true};
+ }
+ } while (optimized_binary_.empty() && ++num_stuck < kMaxNumStuck);
+
+ return {optimized_binary_.empty() ? Status::kStuck : Status::kComplete,
+ !optimized_binary_.empty()};
+}
+
+std::vector<uint32_t> SpirvOptMutator::GetBinary() const {
+ return optimized_binary_;
+}
+
+std::string SpirvOptMutator::GetErrors() const {
+ return errors_.str();
+}
+
+void SpirvOptMutator::LogErrors(const std::string* path, uint32_t count) const {
+ auto message = GetErrors();
+ std::cout << count << " | SpirvOptMutator (seed: " << seed_ << ")"
+ << std::endl;
+ std::cout << message << std::endl;
+
+ if (path) {
+ auto prefix = *path + std::to_string(count);
+
+ // Write errors to file.
+ std::ofstream(prefix + ".opt.log") << "seed: " << seed_ << std::endl
+ << message << std::endl;
+
+ // Write the invalid SPIR-V binary.
+ util::WriteBinary(prefix + ".opt.invalid.spv", optimized_binary_);
+
+ // Write the original SPIR-V binary.
+ util::WriteBinary(prefix + ".opt.original.spv", original_binary_);
+ }
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
new file mode 100644
index 0000000..6d055e4
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_opt_mutator.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
+
+#include <random>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+#include "spirv-tools/libspirv.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Mutates the SPIR-V module using the spirv-opt tool.
+///
+/// The initial `binary` must be valid according to `target_env`. On each call
+/// to the `Mutate` method the mutator selects `opt_batch_size` random
+/// optimization passes (with substitutions) and applies them to the binary.
+class SpirvOptMutator : public Mutator {
+ public:
+ /// Constructor.
+ /// @param target_env - target environment for the `binary`.
+ /// @param seed - seed for the RNG.
+ /// @param binary - SPIR-V binary. Must be valid.
+ /// @param validate_after_each_opt - whether to validate the binary after each
+ /// optimization pass.
+ /// @param opt_batch_size - the maximum number of optimization passes that
+ /// will be applied in a single call to `Mutate`. If it's equal to 0 then
+ /// all available optimization passes are applied.
+ SpirvOptMutator(spv_target_env target_env,
+ uint32_t seed,
+ std::vector<uint32_t> binary,
+ bool validate_after_each_opt,
+ uint32_t opt_batch_size);
+
+ Result Mutate() override;
+ std::vector<uint32_t> GetBinary() const override;
+ void LogErrors(const std::string* path, uint32_t count) const override;
+ std::string GetErrors() const override;
+
+ private:
+ // Number of times this mutator was executed.
+ uint32_t num_executions_;
+
+ // Whether the last execution left it in a valid state.
+ bool is_valid_;
+
+ // Target environment for the SPIR-V binary.
+ const spv_target_env target_env_;
+
+ // The original SPIR-V binary. Useful for debugging.
+ const std::vector<uint32_t> original_binary_;
+
+ // The seed for the RNG. Useful for debugging.
+ const uint32_t seed_;
+
+ // All the optimization passes available.
+ const std::vector<std::string> opt_passes_;
+
+ // The result of the optimization.
+ std::vector<uint32_t> optimized_binary_;
+
+ // Whether we need to validate the binary after each optimization pass.
+ const bool validate_after_each_opt_;
+
+ // The number of optimization passes to apply at once.
+ const uint32_t opt_batch_size_;
+
+ // All the errors produced by the optimizer.
+ std::stringstream errors_;
+
+ // The random number generator initialized with `seed_`.
+ std::mt19937 rng_;
+};
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_OPT_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
new file mode 100644
index 0000000..dccbfe3
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.cc
@@ -0,0 +1,190 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 "fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h"
+
+#include <fstream>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/opt/build_module.h"
+#include "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h"
+#include "source/reduce/merge_blocks_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_const_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
+#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
+#include "source/reduce/remove_block_reduction_opportunity_finder.h"
+#include "source/reduce/remove_function_reduction_opportunity_finder.h"
+#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
+#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
+#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
+#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+SpirvReduceMutator::SpirvReduceMutator(spv_target_env target_env,
+ std::vector<uint32_t> binary,
+ uint32_t seed,
+ uint32_t reductions_batch_size,
+ bool enable_all_reductions,
+ bool validate_after_each_reduction)
+ : ir_context_(nullptr),
+ finders_(),
+ rng_(seed),
+ errors_(),
+ is_valid_(true),
+ reductions_batch_size_(reductions_batch_size),
+ total_applied_reductions_(0),
+ enable_all_reductions_(enable_all_reductions),
+ validate_after_each_reduction_(validate_after_each_reduction),
+ original_binary_(std::move(binary)),
+ seed_(seed) {
+ ir_context_ = spvtools::BuildModule(
+ target_env, spvtools::fuzz::fuzzerutil::kSilentMessageConsumer,
+ original_binary_.data(), original_binary_.size());
+ assert(ir_context_ && "|binary| is invalid");
+
+ do {
+ MaybeAddFinder<
+ spvtools::reduce::
+ ConditionalBranchToSimpleConditionalBranchOpportunityFinder>();
+ MaybeAddFinder<spvtools::reduce::MergeBlocksReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::OperandToConstReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::OperandToDominatingIdReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::OperandToUndefReductionOpportunityFinder>();
+ MaybeAddFinder<spvtools::reduce::RemoveBlockReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::RemoveFunctionReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::RemoveSelectionReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::RemoveUnusedInstructionReductionOpportunityFinder>(
+ true);
+ MaybeAddFinder<
+ spvtools::reduce::RemoveUnusedStructMemberReductionOpportunityFinder>();
+ MaybeAddFinder<
+ spvtools::reduce::SimpleConditionalBranchToBranchOpportunityFinder>();
+ MaybeAddFinder<spvtools::reduce::
+ StructuredLoopToSelectionReductionOpportunityFinder>();
+ } while (finders_.empty());
+}
+
+Mutator::Result SpirvReduceMutator::Mutate() {
+ assert(is_valid_ && "Can't mutate invalid module");
+
+ // The upper limit on the number of applied reduction passes.
+ const uint32_t kMaxAppliedReductions = 500;
+ const auto old_applied_reductions = total_applied_reductions_;
+
+ // The upper limit on the number of failed attempts to apply reductions (i.e.
+ // when no reduction was returned by the reduction finder).
+ const uint32_t kMaxConsecutiveFailures = 10;
+ uint32_t num_consecutive_failures = 0;
+
+ // Iterate while we haven't exceeded the limit on the total number of applied
+ // reductions, the limit on the number of reductions applied at once and limit
+ // on the number of consecutive failed attempts.
+ while (total_applied_reductions_ < kMaxAppliedReductions &&
+ (reductions_batch_size_ == 0 ||
+ total_applied_reductions_ - old_applied_reductions <
+ reductions_batch_size_) &&
+ num_consecutive_failures < kMaxConsecutiveFailures) {
+ // Select an opportunity finder and get some reduction opportunities from
+ // it.
+ auto finder = GetRandomElement(&finders_);
+ auto reduction_opportunities =
+ finder->GetAvailableOpportunities(ir_context_.get(), 0);
+
+ if (reduction_opportunities.empty()) {
+ // There is nothing to reduce. We increase the counter to make sure we
+ // don't stuck in this situation.
+ num_consecutive_failures++;
+ } else {
+ // Apply a random reduction opportunity. The latter should be applicable.
+ auto opportunity = GetRandomElement(&reduction_opportunities);
+ assert(opportunity->PreconditionHolds() && "Preconditions should hold");
+ total_applied_reductions_++;
+ num_consecutive_failures = 0;
+ if (!ApplyReduction(opportunity)) {
+ // The module became invalid as a result of the applied reduction.
+ is_valid_ = false;
+ return {Mutator::Status::kInvalid,
+ total_applied_reductions_ != old_applied_reductions};
+ }
+ }
+ }
+
+ auto is_changed = total_applied_reductions_ != old_applied_reductions;
+ if (total_applied_reductions_ == kMaxAppliedReductions) {
+ return {Mutator::Status::kLimitReached, is_changed};
+ }
+
+ if (num_consecutive_failures == kMaxConsecutiveFailures) {
+ return {Mutator::Status::kStuck, is_changed};
+ }
+
+ assert(is_changed && "This is the only way left to break the loop");
+ return {Mutator::Status::kComplete, is_changed};
+}
+
+bool SpirvReduceMutator::ApplyReduction(
+ spvtools::reduce::ReductionOpportunity* reduction_opportunity) {
+ reduction_opportunity->TryToApply();
+ return !validate_after_each_reduction_ ||
+ spvtools::fuzz::fuzzerutil::IsValidAndWellFormed(
+ ir_context_.get(), spvtools::ValidatorOptions(),
+ util::GetBufferMessageConsumer(&errors_));
+}
+
+std::vector<uint32_t> SpirvReduceMutator::GetBinary() const {
+ std::vector<uint32_t> result;
+ ir_context_->module()->ToBinary(&result, true);
+ return result;
+}
+
+std::string SpirvReduceMutator::GetErrors() const {
+ return errors_.str();
+}
+
+void SpirvReduceMutator::LogErrors(const std::string* path,
+ uint32_t count) const {
+ auto message = GetErrors();
+ std::cout << count << " | SpirvReduceMutator (seed: " << seed_ << ")"
+ << std::endl;
+ std::cout << message << std::endl;
+
+ if (path) {
+ auto prefix = *path + std::to_string(count);
+
+ // Write errors to file.
+ std::ofstream(prefix + ".reducer.log") << "seed: " << seed_ << std::endl
+ << message << std::endl;
+
+ // Write the invalid SPIR-V binary.
+ util::WriteBinary(prefix + ".reducer.invalid.spv", GetBinary());
+
+ // Write the original SPIR-V binary.
+ util::WriteBinary(prefix + ".reducer.original.spv", original_binary_);
+ }
+}
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
new file mode 100644
index 0000000..4d656d8
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/spirv_reduce_mutator.h
@@ -0,0 +1,134 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
+
+#include <memory>
+#include <random>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "source/reduce/reduction_opportunity_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+
+/// Mutates SPIR-V binary by running spirv-reduce tool.
+///
+/// The initial `binary` must be valid according to `target_env`. Applies at
+/// most `reductions_batch_size` reductions at a time. This parameter is ignored
+/// if its value is 0. Uses a random subset of reduction opportunity finders by
+/// default. This can be overridden with the `enable_all_reductions` parameter.
+class SpirvReduceMutator : public Mutator {
+ public:
+ /// Constructor.
+ /// @param target_env - the target environment for the `binary`.
+ /// @param binary - SPIR-V binary. Must be valid.
+ /// @param seed - the seed for the RNG.
+ /// @param reductions_batch_size - the number of reduction passes that will be
+ /// applied during a single call to `Mutate`. If it's equal to 0 then we
+ /// apply the passes until we reach the threshold for the total number of
+ /// applied passes.
+ /// @param enable_all_reductions - whether to use all reduction passes or only
+ /// a randomly selected subset of them.
+ /// @param validate_after_each_reduction - whether to validate after each
+ /// applied reduction.
+ SpirvReduceMutator(spv_target_env target_env,
+ std::vector<uint32_t> binary,
+ uint32_t seed,
+ uint32_t reductions_batch_size,
+ bool enable_all_reductions,
+ bool validate_after_each_reduction);
+
+ Result Mutate() override;
+ std::vector<uint32_t> GetBinary() const override;
+ void LogErrors(const std::string* path, uint32_t count) const override;
+ std::string GetErrors() const override;
+
+ private:
+ template <typename T, typename... Args>
+ void MaybeAddFinder(Args&&... args) {
+ if (enable_all_reductions_ || std::uniform_int_distribution<>(0, 1)(rng_)) {
+ finders_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
+ }
+ }
+
+ template <typename T>
+ T* GetRandomElement(std::vector<T>* arr) {
+ assert(!arr->empty() && "Can't get random element from an empty vector");
+ auto index =
+ std::uniform_int_distribution<size_t>(0, arr->size() - 1)(rng_);
+ return &(*arr)[index];
+ }
+
+ template <typename T>
+ T* GetRandomElement(std::vector<std::unique_ptr<T>>* arr) {
+ assert(!arr->empty() && "Can't get random element from an empty vector");
+ auto index =
+ std::uniform_int_distribution<size_t>(0, arr->size() - 1)(rng_);
+ return (*arr)[index].get();
+ }
+
+ bool ApplyReduction(
+ spvtools::reduce::ReductionOpportunity* reduction_opportunity);
+
+ // The SPIR-V binary that is being reduced.
+ std::unique_ptr<spvtools::opt::IRContext> ir_context_;
+
+ // The selected subset of reduction opportunity finders.
+ std::vector<std::unique_ptr<spvtools::reduce::ReductionOpportunityFinder>>
+ finders_;
+
+ // Random number generator initialized with `seed_`.
+ std::mt19937 rng_;
+
+ // All the errors produced by the reducer.
+ std::stringstream errors_;
+
+ // Whether the last call to the `Mutate` method produced the valid binary.
+ bool is_valid_;
+
+ // The number of reductions to apply on a single call to `Mutate`.
+ const uint32_t reductions_batch_size_;
+
+ // The total number of applied reductions.
+ uint32_t total_applied_reductions_;
+
+ // Whether we want to use all the reduction opportunity finders and not just a
+ // subset of them.
+ const bool enable_all_reductions_;
+
+ // Whether we want to validate all the binary after each reduction.
+ const bool validate_after_each_reduction_;
+
+ // The original binary that was used to initialize this mutator.
+ // Useful for debugging.
+ const std::vector<uint32_t> original_binary_;
+
+ // The seed that was used to initialize the random number generator.
+ // Useful for debugging.
+ const uint32_t seed_;
+};
+
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_SPIRV_REDUCE_MUTATOR_H_
diff --git a/fuzzers/tint_spirv_tools_fuzzer/util.cc b/fuzzers/tint_spirv_tools_fuzzer/util.cc
new file mode 100644
index 0000000..bd574a2
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/util.cc
@@ -0,0 +1,160 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 <fstream>
+#include <iostream>
+
+#include "fuzzers/tint_spirv_tools_fuzzer/util.h"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace util {
+namespace {
+
+bool WriteBinary(const std::string& path, const uint8_t* data, size_t size) {
+ std::ofstream spv(path, std::ios::binary);
+ return spv && spv.write(reinterpret_cast<const char*>(data),
+ static_cast<std::streamsize>(size));
+}
+
+void LogError(uint32_t index,
+ const std::string& type,
+ const std::string& message,
+ const std::string* path,
+ const uint8_t* data,
+ size_t size,
+ const std::string* wgsl) {
+ std::cout << index << " | " << type << ": " << message << std::endl;
+
+ if (path) {
+ auto prefix = *path + std::to_string(index);
+ std::ofstream(prefix + ".log") << message << std::endl;
+
+ WriteBinary(prefix + ".spv", data, size);
+
+ if (wgsl) {
+ std::ofstream(prefix + ".wgsl") << *wgsl << std::endl;
+ }
+ }
+}
+
+} // namespace
+
+spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer) {
+ return [buffer](spv_message_level_t level, const char*,
+ const spv_position_t& position, const char* message) {
+ std::string status;
+ switch (level) {
+ case SPV_MSG_FATAL:
+ case SPV_MSG_INTERNAL_ERROR:
+ case SPV_MSG_ERROR:
+ status = "ERROR";
+ break;
+ case SPV_MSG_WARNING:
+ case SPV_MSG_INFO:
+ case SPV_MSG_DEBUG:
+ status = "INFO";
+ break;
+ }
+ *buffer << status << " " << position.line << ":" << position.column << ":"
+ << position.index << ": " << message << std::endl;
+ };
+}
+
+void LogMutatorError(const Mutator& mutator, const std::string& error_dir) {
+ static uint32_t mutator_count = 0;
+ auto error_path = error_dir.empty() ? error_dir : error_dir + "/mutator/";
+ mutator.LogErrors(error_dir.empty() ? nullptr : &error_path, mutator_count++);
+}
+
+void LogWgslError(const std::string& message,
+ const uint8_t* data,
+ size_t size,
+ const std::string& wgsl,
+ OutputFormat output_format,
+ const std::string& error_dir) {
+ static uint32_t wgsl_count = 0;
+ std::string error_type;
+ switch (output_format) {
+ case OutputFormat::kSpv:
+ error_type = "WGSL -> SPV";
+ break;
+ case OutputFormat::kMSL:
+ error_type = "WGSL -> MSL";
+ break;
+ case OutputFormat::kHLSL:
+ error_type = "WGSL -> HLSL";
+ break;
+ case OutputFormat::kWGSL:
+ error_type = "WGSL -> WGSL";
+ break;
+ default:
+ assert(false && "All output formats should've been handled");
+ break;
+ }
+ auto error_path = error_dir.empty() ? error_dir : error_dir + "/wgsl/";
+ LogError(wgsl_count++, error_type, message,
+ error_dir.empty() ? nullptr : &error_path, data, size, &wgsl);
+}
+
+void LogSpvError(const std::string& message,
+ const uint8_t* data,
+ size_t size,
+ const std::string& error_dir) {
+ static uint32_t spv_count = 0;
+ auto error_path = error_dir.empty() ? error_dir : error_dir + "/spv/";
+ LogError(spv_count++, "SPV -> WGSL", message,
+ error_dir.empty() ? nullptr : &error_path, data, size, nullptr);
+}
+
+bool ReadBinary(const std::string& path, std::vector<uint32_t>* out) {
+ if (!out) {
+ return false;
+ }
+
+ std::ifstream file(path, std::ios::binary | std::ios::ate);
+ if (!file) {
+ return false;
+ }
+
+ auto size = file.tellg();
+ if (!file) {
+ return false;
+ }
+
+ file.seekg(0);
+ if (!file) {
+ return false;
+ }
+
+ std::vector<char> binary(static_cast<size_t>(size));
+ if (!file.read(binary.data(), size)) {
+ return false;
+ }
+
+ out->resize(binary.size() / sizeof(uint32_t));
+ std::memcpy(out->data(), binary.data(), binary.size());
+ return true;
+}
+
+bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary) {
+ return WriteBinary(path, reinterpret_cast<const uint8_t*>(binary.data()),
+ binary.size() * sizeof(uint32_t));
+}
+
+} // namespace util
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
diff --git a/fuzzers/tint_spirv_tools_fuzzer/util.h b/fuzzers/tint_spirv_tools_fuzzer/util.h
new file mode 100644
index 0000000..b5abe47
--- /dev/null
+++ b/fuzzers/tint_spirv_tools_fuzzer/util.h
@@ -0,0 +1,96 @@
+// Copyright 2021 The Tint Authors.
+//
+// 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 FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
+#define FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "fuzzers/tint_common_fuzzer.h"
+#include "fuzzers/tint_spirv_tools_fuzzer/mutator.h"
+
+#include "spirv-tools/libspirv.hpp"
+
+namespace tint {
+namespace fuzzers {
+namespace spvtools_fuzzer {
+namespace util {
+
+/// @param buffer will be used to output errors by the returned message
+/// consumer. Must remain in scope as long as the returned consumer is in
+/// scope.
+/// @return the message consumer that will print errors to the `buffer`.
+spvtools::MessageConsumer GetBufferMessageConsumer(std::stringstream* buffer);
+
+/// Output errors from the SPV -> WGSL conversion.
+///
+/// @param message - the error message.
+/// @param data - invalid SPIR-V binary.
+/// @param size - the size of `data`.
+/// @param error_dir - the directory, to which the binary will be printed to.
+/// If it's empty, the invalid binary and supplemental files will not be
+/// printed. Otherwise, it must have a `spv/` subdirectory.
+void LogSpvError(const std::string& message,
+ const uint8_t* data,
+ size_t size,
+ const std::string& error_dir);
+
+/// Output errors from the WGSL -> `output_format` conversion.
+///
+/// @param message - the error message.
+/// @param data - the SPIR-V binary that generated the WGSL binary.
+/// @param size - the size of `data`.
+/// @param wgsl - the invalid WGSL binary.
+/// @param output_format - the format which we attempted to convert `wgsl` to.
+/// @param error_dir - the directory, to which the binary will be printed out.
+/// If it's empty, the invalid binary and supplemental files will not be
+/// printed. Otherwise, it must have a `wgsl/` subdirectory.
+void LogWgslError(const std::string& message,
+ const uint8_t* data,
+ size_t size,
+ const std::string& wgsl,
+ OutputFormat output_format,
+ const std::string& error_dir);
+
+/// Output errors produced by the mutator.
+///
+/// @param mutator - the mutator with invalid state.
+/// @param error_dir - the directory, to which invalid files will be printed to.
+/// If it's empty, the invalid binary and supplemental files will not be
+/// printed. Otherwise, it must have a `mutator/` subdirectory.
+void LogMutatorError(const Mutator& mutator, const std::string& error_dir);
+
+/// Reads SPIR-V binary from `path` into `out`. Returns `true` if successful and
+/// `false` otherwise (in this case, `out` is unchanged).
+///
+/// @param path - the path to the SPIR-V binary.
+/// @param out - may be a `nullptr`. In this case, `false` is returned.
+/// @return `true` if successful and `false` otherwise.
+bool ReadBinary(const std::string& path, std::vector<uint32_t>* out);
+
+/// Writes `binary` into `path`.
+///
+/// @param path - the path to write `binary` to.
+/// @param binary - SPIR-V binary.
+/// @return whether the operation was successful.
+bool WriteBinary(const std::string& path, const std::vector<uint32_t>& binary);
+
+} // namespace util
+} // namespace spvtools_fuzzer
+} // namespace fuzzers
+} // namespace tint
+
+#endif // FUZZERS_TINT_SPIRV_TOOLS_FUZZER_UTIL_H_
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index 734ebca..230d66e 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -17,6 +17,13 @@
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
endif()
+if (${TINT_BUILD_SPIRV_TOOLS_FUZZER} AND (NOT TARGET protobuf::libprotobuf OR NOT TARGET protobuf::protoc))
+ set(SPIRV_BUILD_FUZZER ON CACHE BOOL "Build spirv-fuzz")
+ set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
+ set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
+ add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/protobuf/cmake)
+endif()
+
if(${TINT_BUILD_SPV_READER} OR ${TINT_BUILD_SPV_WRITER})
if (NOT IS_DIRECTORY "${SPIRV-Headers_SOURCE_DIR}")
set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers CACHE STRING "")