blob: 64b6c4985f3301ee94105e920589874868001744 [file] [log] [blame]
// 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.
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include "src/tint/lang/wgsl/sem/variable.h"
#include "src/tint/api/tint.h"
#include "src/tint/cmd/common/helper.h"
#include "src/tint/lang/core/ir/binary/encode.h"
#include "src/tint/lang/core/ir/disassembler.h"
#include "src/tint/lang/core/ir/module.h"
#include "src/tint/lang/core/ir/validator.h"
#include "src/tint/lang/wgsl/ast/module.h"
#include "src/tint/lang/wgsl/helpers/apply_substitute_overrides.h"
#include "src/tint/lang/wgsl/helpers/flatten_bindings.h"
#include "src/tint/lang/wgsl/reader/reader.h"
#include "src/tint/utils/cli/cli.h"
#include "src/tint/utils/containers/transform.h"
#include "src/tint/utils/macros/defer.h"
#include "src/tint/utils/text/color_mode.h"
#include "src/tint/utils/text/string.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/styled_text_printer.h"
TINT_BEGIN_DISABLE_PROTOBUF_WARNINGS();
#include "src/tint/utils/protos/ir_fuzz/ir_fuzz.pb.h"
TINT_END_DISABLE_PROTOBUF_WARNINGS();
namespace {
struct Options {
std::unique_ptr<tint::StyledTextPrinter> printer;
std::string input_filename;
std::string output_filename;
std::string io_dirname;
bool dump_ir = false;
bool dump_proto = false;
bool verbose = false;
};
bool ParseArgs(tint::VectorRef<std::string_view> arguments, Options* opts) {
using namespace tint::cli; // NOLINT(build/namespaces)
OptionSet options;
auto& col = options.Add<EnumOption<tint::ColorMode>>(
"color", "Use colored output",
tint::Vector{
EnumName{tint::ColorMode::kPlain, "off"},
EnumName{tint::ColorMode::kDark, "dark"},
EnumName{tint::ColorMode::kLight, "light"},
},
ShortName{"col"}, Default{tint::ColorModeDefault()});
TINT_DEFER(opts->printer = CreatePrinter(*col.value));
auto& output = options.Add<StringOption>(
"output-filename", "Output file name, only usable if single input file provided",
ShortName{"o"}, Parameter{"name"});
TINT_DEFER(opts->output_filename = output.value.value_or(""));
auto& dump_ir = options.Add<BoolOption>("dump-ir", "Writes the IR form of input to stdout",
Alias{"emit-ir"}, Default{false});
TINT_DEFER(opts->dump_ir = *dump_ir.value);
auto& dump_proto = options.Add<BoolOption>(
"dump-proto", "Writes the IR in the test case proto as a human readable text to stdout",
Alias{"emit-proto"}, Default{false});
TINT_DEFER(opts->dump_proto = *dump_proto.value);
auto& verbose =
options.Add<BoolOption>("verbose", "Enable additional internal logging", Default{false});
TINT_DEFER(opts->verbose = *verbose.value);
auto& help = options.Add<BoolOption>("help", "Show usage", ShortName{"h"});
auto show_usage = [&] {
std::cout
<< R"(Usage: ir_fuzz_as [options] [-o|--output-filename] <output-file> <input-file> or tint [options] <io-dir>
If a single WGSL file is provided, the suffix of the input file is not checked, and
'-o|--output-filename' must be provided.
If a directory is provided, the files it contains will be scanned and any .wgsl files will have a
corresponding .tirb file generated.
Passing in '-o|--output-filename' when providing a directory will cause a failure.
Options:
)";
options.ShowHelp(std::cout);
};
auto result = options.Parse(arguments);
if (result != tint::Success) {
std::cerr << result.Failure() << "\n";
show_usage();
return false;
}
if (help.value.value_or(false)) {
show_usage();
return false;
}
auto args = result.Get();
if (args.Length() > 1) {
std::cerr << "More than one input arg specified: "
<< tint::Join(Transform(args, tint::Quote), ", ") << "\n";
return false;
}
if (args.Length() == 1) {
if (is_directory(std::filesystem::path{args[0]})) {
opts->io_dirname = args[0];
} else {
opts->input_filename = args[0];
}
}
return true;
}
/// Dumps IR representation for a program.
/// @param program the program to generate
/// @param options the options that ir_fuzz-as was invoked with
/// @returns true on success
bool DumpIR(const tint::Program& program, const Options& options) {
auto result = tint::wgsl::reader::ProgramToLoweredIR(program);
if (result != tint::Success) {
std::cerr << "Failed to build IR from program: " << result.Failure() << "\n";
return false;
}
options.printer->Print(tint::core::ir::Disassembler(result.Get()).Text());
options.printer->Print(tint::StyledText{} << "\n");
return true;
}
/// Generate an IR module for a program, performs checking for unsupported
/// enables, and validation.
/// @param program the program to generate
/// @returns generated module on success, tint::failure on failure
tint::Result<tint::core::ir::Module> GenerateIrModule(const tint::Program& program) {
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;
}
/// @returns a fuzzer test case protobuf for the given program.
/// @param program the program to generate
tint::Result<tint::cmd::fuzz::ir::pb::Root> GenerateFuzzCaseProto(const tint::Program& program) {
auto module = GenerateIrModule(program);
if (module != tint::Success) {
std::cerr << "Failed to generate lowered IR from program: " << module.Failure() << "\n";
return tint::Failure();
}
tint::cmd::fuzz::ir::pb::Root fuzz_pb;
{
auto ir_pb = tint::core::ir::binary::EncodeToProto(module.Get());
fuzz_pb.set_allocated_module(ir_pb.release());
}
return std::move(fuzz_pb);
}
/// Write out fuzzer test case protobuf in binary format
/// @param proto test case proto to write out
/// @param options the options that ir_fuzz_as was invoked with
/// @returns true on success
bool WriteTestCaseProto(const tint::cmd::fuzz::ir::pb::Root& proto, const Options& options) {
tint::Vector<std::byte, 0> buffer;
size_t len = proto.ByteSizeLong();
buffer.Resize(len);
if (len > 0) {
if (!proto.SerializeToArray(&buffer[0], static_cast<int>(len))) {
std::cerr << "Failed to serialize test case protobuf";
return false;
}
}
if (!tint::cmd::WriteFile(options.output_filename, "wb", ToStdVector(buffer))) {
std::cerr << "Failed to write protobuf binary out to file '" << options.output_filename
<< "'\n";
return false;
}
return true;
}
/// Dumps IR from test case proto in a human-readable format
/// @param proto test case proto to dump IR from
/// @param options the options that ir_fuzz_as was invoked with
void DumpTestCaseProtoDebug(const tint::cmd::fuzz::ir::pb::Root& proto, const Options& options) {
options.printer->Print(tint::StyledText{} << proto.module().DebugString());
options.printer->Print(tint::StyledText{} << "\n");
}
bool ProcessFile(const Options& options) {
if (options.verbose) {
options.printer->Print(tint::StyledText{} << "Processing '" << options.input_filename
<< "'\n");
}
tint::cmd::LoadProgramOptions opts;
opts.filename = options.input_filename;
opts.printer = options.printer.get();
auto info = tint::cmd::LoadProgramInfo(opts);
if (options.dump_ir) {
DumpIR(info.program, options);
}
auto proto = GenerateFuzzCaseProto(info.program);
if (proto != tint::Success) {
return false;
}
if (options.dump_proto) {
DumpTestCaseProtoDebug(proto.Get(), options);
}
if (!options.output_filename.empty()) {
if (!WriteTestCaseProto(proto.Get(), options)) {
return false;
}
}
return true;
}
} // namespace
int main(int argc, const char** argv) {
tint::Vector<std::string_view, 8> arguments;
for (int i = 1; i < argc; i++) {
std::string_view arg(argv[i]);
if (!arg.empty()) {
arguments.Push(argv[i]);
}
}
Options options;
tint::Initialize();
tint::SetInternalCompilerErrorReporter(&tint::cmd::TintInternalCompilerErrorReporter);
if (!ParseArgs(arguments, &options)) {
return EXIT_FAILURE;
}
if (!options.input_filename.empty() && !options.io_dirname.empty()) {
std::cerr << "Somehow both input_filename '" << options.input_filename
<< ", and io_dirname '" << options.io_dirname
<< "' were set after parsing arguments\n";
return EXIT_FAILURE;
}
if (options.output_filename.empty() && !options.dump_ir && !options.dump_proto &&
options.io_dirname.empty()) {
std::cerr << "None of --output-name, --dump-ir, --dump-proto, or <io-dir> were provided, "
"so no output would be generated...\n";
return EXIT_FAILURE;
}
if (!options.input_filename.empty()) {
if (!ProcessFile(options)) {
return EXIT_FAILURE;
}
} else {
tint::Vector<std::string, 8> wgsl_filenames;
// Need to collect the WGSL filenames and then process them in a second phase, so that the
// contents of the directory isn't changing during the iteration.
for (auto const& io_entry :
std::filesystem::directory_iterator{std::filesystem::path{options.io_dirname}}) {
const std::string entry_filename = io_entry.path().string();
if (entry_filename.substr(entry_filename.size() - 5) == ".wgsl") {
wgsl_filenames.Push(std::move(entry_filename));
}
}
for (auto const& input_filename : wgsl_filenames) {
const auto output_filename =
std::string(input_filename.substr(0, input_filename.size() - 5)) + ".tirb";
options.input_filename = input_filename;
options.output_filename = output_filename;
ProcessFile(options); // Ignoring the return value, so that one bad file doesn't cause
// the processing batch to stop.
}
}
return EXIT_SUCCESS;
}