Add generating Tint IR fuzzer corpus
This adds in a Python script for generating IR binary file from a WGSL
file, build rules for running this over the testing WGSL files, and
changes to the IR binary outputting code to reduce the number of
failures due to unsupported inputs.
Currently 7203 out of 7275 shaders that are used in the WGSL corpus
are convertable. Those that are not, either use enables that are not
supported in the IR, or fail for reasons like missing override
values. The generation code is resiliant enough that these failure do
not break process, so investigating those failing shaders can be done
in a follow up CL/issue.
Issue: 341114373
Change-Id: Ic7b26010465865c46b1013db3a0613d89e0db457
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188662
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index af3d7a5..2544db7 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -275,7 +275,7 @@
if (tint_has_fuzzers) {
action("tint_generate_wgsl_corpus") {
script = "${tint_src_dir}/cmd/fuzz/wgsl/generate_wgsl_corpus.py"
- sources = [ "${tint_src_dir}/cmd/fuzz/wgsl/generate_wgsl_corpus.py" ]
+ sources = [ "${script}" ]
args = [
"--stamp=" + rebase_path(fuzzer_corpus_wgsl_stamp, root_build_dir),
rebase_path("${tint_root_dir}/test", root_build_dir),
@@ -283,6 +283,23 @@
]
outputs = [ fuzzer_corpus_wgsl_stamp ]
}
+
+ if (tint_build_cmds) {
+ if (tint_build_wgsl_reader && tint_build_ir_binary) {
+ action("tint_generate_ir_corpus") {
+ script = "${tint_src_dir}/cmd/fuzz/wgsl/generate_ir_corpus.py"
+ sources = [ "${script}" ]
+ deps = [ "${tint_src_dir}/cmd/tint" ]
+ args = [
+ "--stamp=" + rebase_path(fuzzer_corpus_ir_stamp, root_build_dir),
+ rebase_path("${root_build_dir}/tint", root_build_dir),
+ rebase_path("${tint_root_dir}/test", root_build_dir),
+ rebase_path(fuzzer_corpus_ir_dir, root_build_dir),
+ ]
+ outputs = [ fuzzer_corpus_ir_stamp ]
+ }
+ }
+ }
}
###############################################################################
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index b726fb7..08f9568 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -41,32 +41,13 @@
#if TINT_BUILD_WGSL_READER
namespace tint::fuzz::ir {
-namespace {
-
-bool IsUnsupported(const ast::Enable* enable) {
- for (auto ext : enable->extensions) {
- switch (ext->name) {
- case tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch:
- case tint::wgsl::Extension::kChromiumExperimentalPixelLocal:
- case tint::wgsl::Extension::kChromiumExperimentalPushConstant:
- case tint::wgsl::Extension::kChromiumInternalDualSourceBlending:
- case tint::wgsl::Extension::kChromiumInternalRelaxedUniformLayout:
- return true;
- default:
- break;
- }
- }
- return false;
-}
-
-} // namespace
void Register(const IRFuzzer& fuzzer) {
wgsl::Register({
fuzzer.name,
[fn = fuzzer.fn](const Program& program, const fuzz::wgsl::Context& context,
Slice<const std::byte> data) {
- if (program.AST().Enables().Any(IsUnsupported)) {
+ if (program.AST().Enables().Any(tint::wgsl::reader::IsUnsupportedByIR)) {
return;
}
diff --git a/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py b/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py
new file mode 100644
index 0000000..c332259
--- /dev/null
+++ b/src/tint/cmd/fuzz/wgsl/generate_ir_corpus.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Copyright 2024 The Dawn & Tint Authors
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Collect all .wgsl files under a given directory and convert them to IR
+# protobuf in a given corpus directory, flattening their file names by replacing
+# path separators with underscores. If the output directory already exists, it
+# will be deleted and re-created. Files ending with ".expected.wgsl" are
+# skipped.
+#
+# The intended use of this script is to generate a corpus of IR protobufs
+# for fuzzing.
+#
+# Based off of generate_wgsl_corpus.py
+#
+# Usage:
+# generate_ir_corpus.py <path to tint cmd> <input_dir> <corpus_dir>
+
+import optparse
+import subprocess
+
+import os
+import pathlib
+import shutil
+import sys
+
+
+def list_wgsl_files(root_search_dir):
+ for root, folders, files in os.walk(root_search_dir):
+ for filename in folders + files:
+ if pathlib.Path(filename).suffix == '.wgsl':
+ yield os.path.join(root, filename)
+
+
+def main():
+ parser = optparse.OptionParser(
+ usage="usage: %prog [option] tint-cmd input-dir output-dir")
+ parser.add_option('--stamp', dest='stamp', help='stamp file')
+ options, args = parser.parse_args(sys.argv[1:])
+
+ if len(args) != 3:
+ parser.error("incorrect number of arguments")
+
+ tint_cmd: str = os.path.abspath(args[0])
+ if not os.path.isfile(tint_cmd) or not os.access(tint_cmd, os.X_OK):
+ parser.error("Unable to run tint-cmd '" + os.path.abspath(args[0]) +
+ "' (" + tint_cmd + ")")
+
+ input_dir: str = os.path.abspath(args[1].rstrip(os.sep))
+ corpus_dir: str = os.path.abspath(args[2])
+
+ if os.path.exists(corpus_dir):
+ shutil.rmtree(corpus_dir)
+ os.makedirs(corpus_dir)
+
+ for in_file in list_wgsl_files(input_dir):
+ if in_file.endswith(".expected.wgsl"):
+ continue
+
+ out_file = os.path.splitext(in_file[len(input_dir) + 1:].replace(
+ os.sep, '_'))[0] + '.tirb'
+ subprocess.run([
+ tint_cmd, '--format=ir_bin',
+ '--output-name=' + corpus_dir + os.sep + out_file, in_file
+ ],
+ stderr=subprocess.STDOUT)
+
+ if options.stamp:
+ pathlib.Path(options.stamp).touch(mode=0o644, exist_ok=True)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index ccfdcf1..5203fec 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -71,6 +71,8 @@
#if TINT_BUILD_IR_BINARY
#include "src/tint/lang/core/ir/binary/encode.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/wgsl/helpers/apply_substitute_overrides.h"
#endif // TINT_BUILD_IR_BINARY
#endif // TINT_BUILD_WGSL_READER
@@ -1259,6 +1261,37 @@
#endif
}
+/// Generate an IR module for a program, performs checking for unsupported
+/// enables, needed transforms, and validation.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns generated module on success, tint::failure on failure
+#if WGSL_READER_AND_IR_BINARY
+tint::Result<tint::core::ir::Module> GenerateIrModule([[maybe_unused]] const tint::Program& program,
+ [[maybe_unused]] const Options& options) {
+ if (program.AST().Enables().Any(tint::wgsl::reader::IsUnsupportedByIR)) {
+ return tint::Failure{"Unsupported enable used in shader"};
+ }
+
+ auto transformed = tint::wgsl::ApplySubstituteOverrides(program);
+ auto& src = transformed ? transformed.value() : program;
+ if (!src.IsValid()) {
+ return tint::Failure{src.Diagnostics()};
+ }
+
+ auto ir = tint::wgsl::reader::ProgramToLoweredIR(src);
+ if (ir != tint::Success) {
+ return ir.Failure();
+ }
+
+ if (auto val = tint::core::ir::Validate(ir.Get()); val != tint::Success) {
+ return val.Failure();
+ }
+
+ return ir;
+}
+#endif // WGSL_READER_AND_IR_BINARY
+
/// Generate IR binary protobuf for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
@@ -1272,11 +1305,12 @@
std::cerr << "IR binary not enabled in tint build" << std::endl;
return false;
#else
- auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+ auto module = GenerateIrModule(program, options);
if (module != tint::Success) {
- std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+ std::cerr << "Failed to generate lowered IR from program: " << module.Failure() << "\n";
return false;
}
+
auto pb = tint::core::ir::binary::Encode(module.Get());
if (pb != tint::Success) {
std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
@@ -1300,11 +1334,12 @@
#if WGSL_READER_AND_IR_BINARY
bool GenerateIrProtoDebug([[maybe_unused]] const tint::Program& program,
[[maybe_unused]] const Options& options) {
- auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+ auto module = GenerateIrModule(program, options);
if (module != tint::Success) {
- std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+ std::cerr << "Failed to generate lowered IR from program: " << module.Failure() << "\n";
return false;
}
+
auto pb = tint::core::ir::binary::EncodeDebug(module.Get());
if (pb != tint::Success) {
std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index ed8009a..d446964 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -77,4 +77,20 @@
return ir;
}
+bool IsUnsupportedByIR(const ast::Enable* enable) {
+ for (auto ext : enable->extensions) {
+ switch (ext->name) {
+ case tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch:
+ case tint::wgsl::Extension::kChromiumExperimentalPixelLocal:
+ case tint::wgsl::Extension::kChromiumExperimentalPushConstant:
+ case tint::wgsl::Extension::kChromiumInternalDualSourceBlending:
+ case tint::wgsl::Extension::kChromiumInternalRelaxedUniformLayout:
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
} // namespace tint::wgsl::reader
diff --git a/src/tint/lang/wgsl/reader/reader.h b/src/tint/lang/wgsl/reader/reader.h
index 45edcd8..991e3e0 100644
--- a/src/tint/lang/wgsl/reader/reader.h
+++ b/src/tint/lang/wgsl/reader/reader.h
@@ -32,6 +32,10 @@
#include "src/tint/lang/wgsl/program/program.h"
#include "src/tint/lang/wgsl/reader/options.h"
+namespace tint::ast {
+class Enable;
+} // namespace tint::ast
+
namespace tint::wgsl::reader {
/// Parses the WGSL source, returning the parsed program.
@@ -58,6 +62,10 @@
/// concrete types.
tint::Result<core::ir::Module> ProgramToLoweredIR(const Program& program);
+/// Allows for checking if an extension is currently supported/unsupported by IR
+/// before trying to convert to it.
+bool IsUnsupportedByIR(const ast::Enable* enable);
+
} // namespace tint::wgsl::reader
#endif // SRC_TINT_LANG_WGSL_READER_READER_H_
diff --git a/src/tint/tint.gni b/src/tint/tint.gni
index bf48aae..8f3d528 100644
--- a/src/tint/tint.gni
+++ b/src/tint/tint.gni
@@ -133,6 +133,8 @@
import("//testing/libfuzzer/fuzzer_test.gni")
fuzzer_corpus_wgsl_dir = "${root_gen_dir}/fuzzers/wgsl_corpus"
fuzzer_corpus_wgsl_stamp = "${fuzzer_corpus_wgsl_dir}.stamp"
+ fuzzer_corpus_ir_dir = "${root_gen_dir}/fuzzers/ir_corpus"
+ fuzzer_corpus_ir_stamp = "${fuzzer_corpus_ir_dir}.stamp"
template("tint_fuzz_source_set") {
source_set(target_name) {