blob: a30a133196a1f843d5baa8699ca1ec65a34df5bc [file] [log] [blame]
// Copyright 2020 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 <charconv>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#if TINT_BUILD_GLSL_WRITER
#include "glslang/Public/ResourceLimits.h"
#include "glslang/Public/ShaderLang.h"
#endif // TINT_BUILD_GLSL_WRITER
#if TINT_BUILD_SPV_READER
#include "spirv-tools/libspirv.hpp"
#endif // TINT_BUILD_SPV_READER
#include "src/tint/ast/module.h"
#include "src/tint/utils/io/command.h"
#include "src/tint/utils/string.h"
#include "src/tint/utils/transform.h"
#include "src/tint/val/val.h"
#include "tint/tint.h"
#if TINT_BUILD_IR
#include "src/tint/ir/debug.h"
#include "src/tint/ir/disassembler.h"
#include "src/tint/ir/module.h"
#endif // TINT_BUILD_IR
namespace {
[[noreturn]] void TintInternalCompilerErrorReporter(const tint::diag::List& diagnostics) {
auto printer = tint::diag::Printer::create(stderr, true);
tint::diag::Formatter{}.format(diagnostics, printer.get());
tint::diag::Style bold_red{tint::diag::Color::kRed, true};
constexpr const char* please_file_bug = R"(
********************************************************************
* The tint shader compiler has encountered an unexpected error. *
* *
* Please help us fix this issue by submitting a bug report at *
* crbug.com/tint with the source program that triggered the bug. *
********************************************************************
)";
printer->write(please_file_bug, bold_red);
exit(1);
}
/// Prints the given hash value in a format string that the end-to-end test runner can parse.
void PrintHash(uint32_t hash) {
std::cout << "<<HASH: 0x" << std::hex << hash << ">>" << std::endl;
}
enum class Format {
kUnknown,
kNone,
kSpirv,
kSpvAsm,
kWgsl,
kMsl,
kHlsl,
kGlsl,
};
struct Options {
bool show_help = false;
bool verbose = false;
std::string input_filename;
std::string output_file = "-"; // Default to stdout
bool parse_only = false;
bool disable_workgroup_init = false;
bool validate = false;
bool print_hash = false;
bool demangle = false;
bool dump_inspector_bindings = false;
std::unordered_set<uint32_t> skip_hash;
Format format = Format::kUnknown;
bool emit_single_entry_point = false;
std::string ep_name;
bool rename_all = false;
std::vector<std::string> transforms;
std::string fxc_path;
std::string dxc_path;
std::string xcrun_path;
std::unordered_map<std::string, double> overrides;
std::optional<tint::sem::BindingPoint> hlsl_root_constant_binding_point;
#if TINT_BUILD_IR
bool dump_ir = false;
bool dump_ir_graph = false;
#endif // TINT_BUILD_IR
};
const char kUsage[] = R"(Usage: tint [options] <input-file>
options:
--format <spirv|spvasm|wgsl|msl|hlsl|none> -- Output format.
If not provided, will be inferred from output
filename extension:
.spvasm -> spvasm
.spv -> spirv
.wgsl -> wgsl
.metal -> msl
.hlsl -> hlsl
If none matches, then default to SPIR-V assembly.
-ep <name> -- Output single entry point
--output-file <name> -- Output file name. Use "-" for standard output
-o <name> -- Output file name. Use "-" for standard output
--transform <name list> -- Runs transforms, name list is comma separated
Available transforms:
${transforms} --parse-only -- Stop after parsing the input
--disable-workgroup-init -- Disable workgroup memory zero initialization.
--demangle -- Preserve original source names. Demangle them.
Affects AST dumping, and text-based output languages.
--dump-inspector-bindings -- Dump reflection data about bindins to stdout.
-h -- This help text
--hlsl-root-constant-binding-point <group>,<binding> -- Binding point for root constant.
Specify the binding point for generated uniform buffer
used for num_workgroups in HLSL. If not specified, then
default to binding 0 of the largest used group plus 1,
or group 0 if no resource bound.
--validate -- Validates the generated shader with all available validators
--skip-hash <hash list> -- Skips validation if the hash of the output is equal to any
of the hash codes in the comma separated list of hashes
--print-hash -- Emit the hash of the output program
--fxc -- Path to FXC dll, used to validate HLSL output.
When specified, automatically enables HLSL validation with FXC
--dxc -- Path to DXC executable, used to validate HLSL output.
When specified, automatically enables HLSL validation with DXC
--xcrun -- Path to xcrun executable, used to validate MSL output.
When specified, automatically enables MSL validation
--overrides -- Override values as IDENTIFIER=VALUE, comma-separated.
--rename-all -- Renames all symbols.
)";
Format parse_format(const std::string& fmt) {
(void)fmt;
#if TINT_BUILD_SPV_WRITER
if (fmt == "spirv") {
return Format::kSpirv;
}
if (fmt == "spvasm") {
return Format::kSpvAsm;
}
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_WGSL_WRITER
if (fmt == "wgsl") {
return Format::kWgsl;
}
#endif // TINT_BUILD_WGSL_WRITER
#if TINT_BUILD_MSL_WRITER
if (fmt == "msl") {
return Format::kMsl;
}
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
if (fmt == "hlsl") {
return Format::kHlsl;
}
#endif // TINT_BUILD_HLSL_WRITER
#if TINT_BUILD_GLSL_WRITER
if (fmt == "glsl") {
return Format::kGlsl;
}
#endif // TINT_BUILD_GLSL_WRITER
if (fmt == "none") {
return Format::kNone;
}
return Format::kUnknown;
}
#if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || TINT_BUILD_MSL_WRITER || \
TINT_BUILD_HLSL_WRITER
/// @param input input string
/// @param suffix potential suffix string
/// @returns true if input ends with the given suffix.
bool ends_with(const std::string& input, const std::string& suffix) {
const auto input_len = input.size();
const auto suffix_len = suffix.size();
// Avoid integer overflow.
return (input_len >= suffix_len) && (input_len - suffix_len == input.rfind(suffix));
}
#endif
/// @param filename the filename to inspect
/// @returns the inferred format for the filename suffix
Format infer_format(const std::string& filename) {
(void)filename;
#if TINT_BUILD_SPV_WRITER
if (ends_with(filename, ".spv")) {
return Format::kSpirv;
}
if (ends_with(filename, ".spvasm")) {
return Format::kSpvAsm;
}
#endif // TINT_BUILD_SPV_WRITER
#if TINT_BUILD_WGSL_WRITER
if (ends_with(filename, ".wgsl")) {
return Format::kWgsl;
}
#endif // TINT_BUILD_WGSL_WRITER
#if TINT_BUILD_MSL_WRITER
if (ends_with(filename, ".metal")) {
return Format::kMsl;
}
#endif // TINT_BUILD_MSL_WRITER
#if TINT_BUILD_HLSL_WRITER
if (ends_with(filename, ".hlsl")) {
return Format::kHlsl;
}
#endif // TINT_BUILD_HLSL_WRITER
return Format::kUnknown;
}
std::vector<std::string> split_on_char(std::string list, char c) {
std::vector<std::string> res;
std::stringstream str(list);
while (str.good()) {
std::string substr;
getline(str, substr, c);
res.push_back(substr);
}
return res;
}
std::vector<std::string> split_on_comma(std::string list) {
return split_on_char(list, ',');
}
std::vector<std::string> split_on_equal(std::string list) {
return split_on_char(list, '=');
}
std::optional<uint64_t> parse_unsigned_number(std::string number) {
for (char c : number) {
if (!std::isdigit(c)) {
// Found a non-digital char, return nullopt
return std::nullopt;
}
}
errno = 0;
char* p_end;
uint64_t result;
// std::strtoull will not throw exception.
result = std::strtoull(number.c_str(), &p_end, 10);
if ((errno != 0) || (static_cast<size_t>(p_end - number.c_str()) != number.length())) {
// Unexpected conversion result
return std::nullopt;
}
return result;
}
std::string TextureDimensionToString(tint::inspector::ResourceBinding::TextureDimension dim) {
switch (dim) {
case tint::inspector::ResourceBinding::TextureDimension::kNone:
return "None";
case tint::inspector::ResourceBinding::TextureDimension::k1d:
return "1d";
case tint::inspector::ResourceBinding::TextureDimension::k2d:
return "2d";
case tint::inspector::ResourceBinding::TextureDimension::k2dArray:
return "2dArray";
case tint::inspector::ResourceBinding::TextureDimension::k3d:
return "3d";
case tint::inspector::ResourceBinding::TextureDimension::kCube:
return "Cube";
case tint::inspector::ResourceBinding::TextureDimension::kCubeArray:
return "CubeArray";
}
return "Unknown";
}
std::string SampledKindToString(tint::inspector::ResourceBinding::SampledKind kind) {
switch (kind) {
case tint::inspector::ResourceBinding::SampledKind::kFloat:
return "Float";
case tint::inspector::ResourceBinding::SampledKind::kUInt:
return "UInt";
case tint::inspector::ResourceBinding::SampledKind::kSInt:
return "SInt";
case tint::inspector::ResourceBinding::SampledKind::kUnknown:
break;
}
return "Unknown";
}
std::string TexelFormatToString(tint::inspector::ResourceBinding::TexelFormat format) {
switch (format) {
case tint::inspector::ResourceBinding::TexelFormat::kR32Uint:
return "R32Uint";
case tint::inspector::ResourceBinding::TexelFormat::kR32Sint:
return "R32Sint";
case tint::inspector::ResourceBinding::TexelFormat::kR32Float:
return "R32Float";
case tint::inspector::ResourceBinding::TexelFormat::kBgra8Unorm:
return "Bgra8Unorm";
case tint::inspector::ResourceBinding::TexelFormat::kRgba8Unorm:
return "Rgba8Unorm";
case tint::inspector::ResourceBinding::TexelFormat::kRgba8Snorm:
return "Rgba8Snorm";
case tint::inspector::ResourceBinding::TexelFormat::kRgba8Uint:
return "Rgba8Uint";
case tint::inspector::ResourceBinding::TexelFormat::kRgba8Sint:
return "Rgba8Sint";
case tint::inspector::ResourceBinding::TexelFormat::kRg32Uint:
return "Rg32Uint";
case tint::inspector::ResourceBinding::TexelFormat::kRg32Sint:
return "Rg32Sint";
case tint::inspector::ResourceBinding::TexelFormat::kRg32Float:
return "Rg32Float";
case tint::inspector::ResourceBinding::TexelFormat::kRgba16Uint:
return "Rgba16Uint";
case tint::inspector::ResourceBinding::TexelFormat::kRgba16Sint:
return "Rgba16Sint";
case tint::inspector::ResourceBinding::TexelFormat::kRgba16Float:
return "Rgba16Float";
case tint::inspector::ResourceBinding::TexelFormat::kRgba32Uint:
return "Rgba32Uint";
case tint::inspector::ResourceBinding::TexelFormat::kRgba32Sint:
return "Rgba32Sint";
case tint::inspector::ResourceBinding::TexelFormat::kRgba32Float:
return "Rgba32Float";
case tint::inspector::ResourceBinding::TexelFormat::kNone:
return "None";
}
return "Unknown";
}
std::string ResourceTypeToString(tint::inspector::ResourceBinding::ResourceType type) {
switch (type) {
case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
return "UniformBuffer";
case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
return "StorageBuffer";
case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer:
return "ReadOnlyStorageBuffer";
case tint::inspector::ResourceBinding::ResourceType::kSampler:
return "Sampler";
case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
return "ComparisonSampler";
case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
return "SampledTexture";
case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture:
return "MultisampledTexture";
case tint::inspector::ResourceBinding::ResourceType::kWriteOnlyStorageTexture:
return "WriteOnlyStorageTexture";
case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
return "DepthTexture";
case tint::inspector::ResourceBinding::ResourceType::kDepthMultisampledTexture:
return "DepthMultisampledTexture";
case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
return "ExternalTexture";
}
return "Unknown";
}
bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
for (size_t i = 1; i < args.size(); ++i) {
const std::string& arg = args[i];
if (arg == "--format") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for --format argument." << std::endl;
return false;
}
opts->format = parse_format(args[i]);
if (opts->format == Format::kUnknown) {
std::cerr << "Unknown output format: " << args[i] << std::endl;
return false;
}
} else if (arg == "-ep") {
if (i + 1 >= args.size()) {
std::cerr << "Missing value for -ep" << std::endl;
return false;
}
i++;
opts->ep_name = args[i];
opts->emit_single_entry_point = true;
} else if (arg == "-o" || arg == "--output-name") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
opts->output_file = args[i];
} else if (arg == "-h" || arg == "--help") {
opts->show_help = true;
} else if (arg == "-v" || arg == "--verbose") {
opts->verbose = true;
} else if (arg == "--transform") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
opts->transforms = split_on_comma(args[i]);
} else if (arg == "--parse-only") {
opts->parse_only = true;
} else if (arg == "--disable-workgroup-init") {
opts->disable_workgroup_init = true;
} else if (arg == "--demangle") {
opts->demangle = true;
} else if (arg == "--dump-inspector-bindings") {
opts->dump_inspector_bindings = true;
} else if (arg == "--validate") {
opts->validate = true;
} else if (arg == "--skip-hash") {
++i;
if (i >= args.size()) {
std::cerr << "Missing hash value for " << arg << std::endl;
return false;
}
for (auto hash : split_on_comma(args[i])) {
uint32_t value = 0;
int base = 10;
if (hash.size() > 2 && hash[0] == '0' && (hash[1] == 'x' || hash[1] == 'X')) {
hash = hash.substr(2);
base = 16;
}
std::from_chars(hash.data(), hash.data() + hash.size(), value, base);
opts->skip_hash.emplace(value);
}
} else if (arg == "--print-hash") {
opts->print_hash = true;
} else if (arg == "--fxc") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
opts->fxc_path = args[i];
} else if (arg == "--dxc") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
opts->dxc_path = args[i];
#if TINT_BUILD_IR
} else if (arg == "--dump-ir") {
opts->dump_ir = true;
} else if (arg == "--dump-ir-graph") {
opts->dump_ir_graph = true;
#endif // TINT_BUILD_IR
} else if (arg == "--xcrun") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
opts->xcrun_path = args[i];
opts->validate = true;
} else if (arg == "--overrides") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
for (const auto& o : split_on_comma(args[i])) {
auto parts = split_on_equal(o);
opts->overrides.insert({parts[0], std::stod(parts[1])});
}
} else if (arg == "--rename-all") {
++i;
opts->rename_all = true;
} else if (arg == "--hlsl-root-constant-binding-point") {
++i;
if (i >= args.size()) {
std::cerr << "Missing value for " << arg << std::endl;
return false;
}
auto binding_points = split_on_comma(args[i]);
if (binding_points.size() != 2) {
std::cerr << "Invalid binding point for " << arg << ": " << args[i] << std::endl;
return false;
}
auto group = parse_unsigned_number(binding_points[0]);
if ((!group.has_value()) || (group.value() > std::numeric_limits<uint32_t>::max())) {
std::cerr << "Invalid group for " << arg << ": " << binding_points[0] << std::endl;
return false;
}
auto binding = parse_unsigned_number(binding_points[1]);
if ((!binding.has_value()) ||
(binding.value() > std::numeric_limits<uint32_t>::max())) {
std::cerr << "Invalid binding for " << arg << ": " << binding_points[1]
<< std::endl;
return false;
}
opts->hlsl_root_constant_binding_point = tint::sem::BindingPoint{
static_cast<uint32_t>(group.value()), static_cast<uint32_t>(binding.value())};
} else if (!arg.empty()) {
if (arg[0] == '-') {
std::cerr << "Unrecognized option: " << arg << std::endl;
return false;
}
if (!opts->input_filename.empty()) {
std::cerr << "More than one input file specified: '" << opts->input_filename
<< "' and '" << arg << "'" << std::endl;
return false;
}
opts->input_filename = arg;
}
}
return true;
}
/// Copies the content from the file named `input_file` to `buffer`,
/// assuming each element in the file is of type `T`. If any error occurs,
/// writes error messages to the standard error stream and returns false.
/// Assumes the size of a `T` object is divisible by its required alignment.
/// @returns true if we successfully read the file.
template <typename T>
bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
if (!buffer) {
std::cerr << "The buffer pointer was null" << std::endl;
return false;
}
FILE* file = nullptr;
#if defined(_MSC_VER)
fopen_s(&file, input_file.c_str(), "rb");
#else
file = fopen(input_file.c_str(), "rb");
#endif
if (!file) {
std::cerr << "Failed to open " << input_file << std::endl;
return false;
}
fseek(file, 0, SEEK_END);
const auto file_size = static_cast<size_t>(ftell(file));
if (0 != (file_size % sizeof(T))) {
std::cerr << "File " << input_file
<< " does not contain an integral number of objects: " << file_size
<< " bytes in the file, require " << sizeof(T) << " bytes per object"
<< std::endl;
fclose(file);
return false;
}
fseek(file, 0, SEEK_SET);
buffer->clear();
buffer->resize(file_size / sizeof(T));
size_t bytes_read = fread(buffer->data(), 1, file_size, file);
fclose(file);
if (bytes_read != file_size) {
std::cerr << "Failed to read " << input_file << std::endl;
return false;
}
return true;
}
/// Writes the given `buffer` into the file named as `output_file` using the
/// given `mode`. If `output_file` is empty or "-", writes to standard
/// output. If any error occurs, returns false and outputs error message to
/// standard error. The ContainerT type must have data() and size() methods,
/// like `std::string` and `std::vector` do.
/// @returns true on success
template <typename ContainerT>
bool WriteFile(const std::string& output_file, const std::string mode, const ContainerT& buffer) {
const bool use_stdout = output_file.empty() || output_file == "-";
FILE* file = stdout;
if (!use_stdout) {
#if defined(_MSC_VER)
fopen_s(&file, output_file.c_str(), mode.c_str());
#else
file = fopen(output_file.c_str(), mode.c_str());
#endif
if (!file) {
std::cerr << "Could not open file " << output_file << " for writing" << std::endl;
return false;
}
}
size_t written =
fwrite(buffer.data(), sizeof(typename ContainerT::value_type), buffer.size(), file);
if (buffer.size() != written) {
if (use_stdout) {
std::cerr << "Could not write all output to standard output" << std::endl;
} else {
std::cerr << "Could not write to file " << output_file << std::endl;
fclose(file);
}
return false;
}
if (!use_stdout) {
fclose(file);
}
return true;
}
#if TINT_BUILD_SPV_WRITER
std::string Disassemble(const std::vector<uint32_t>& data) {
std::string spv_errors;
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
const spv_position_t& position, const char* message) {
switch (level) {
case SPV_MSG_FATAL:
case SPV_MSG_INTERNAL_ERROR:
case SPV_MSG_ERROR:
spv_errors +=
"error: line " + std::to_string(position.index) + ": " + message + "\n";
break;
case SPV_MSG_WARNING:
spv_errors +=
"warning: line " + std::to_string(position.index) + ": " + message + "\n";
break;
case SPV_MSG_INFO:
spv_errors +=
"info: line " + std::to_string(position.index) + ": " + message + "\n";
break;
case SPV_MSG_DEBUG:
break;
}
};
spvtools::SpirvTools tools(target_env);
tools.SetMessageConsumer(msg_consumer);
std::string result;
if (!tools.Disassemble(
data, &result,
SPV_BINARY_TO_TEXT_OPTION_INDENT | SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) {
std::cerr << spv_errors << std::endl;
}
return result;
}
#endif // TINT_BUILD_SPV_WRITER
/// PrintWGSL writes the WGSL of the program to the provided ostream, if the
/// WGSL writer is enabled, otherwise it does nothing.
/// @param out the output stream to write the WGSL to
/// @param program the program
void PrintWGSL(std::ostream& out, const tint::Program& program) {
#if TINT_BUILD_WGSL_WRITER
tint::writer::wgsl::Options options;
auto result = tint::writer::wgsl::Generate(&program, options);
out << std::endl << result.wgsl << std::endl;
#else
(void)out;
(void)program;
#endif
}
/// Generate SPIR-V code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool GenerateSpirv(const tint::Program* program, const Options& options) {
#if TINT_BUILD_SPV_WRITER
// TODO(jrprice): Provide a way for the user to set non-default options.
tint::writer::spirv::Options gen_options;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.generate_external_texture_bindings = true;
auto result = tint::writer::spirv::Generate(program, gen_options);
if (!result.success) {
PrintWGSL(std::cerr, *program);
std::cerr << "Failed to generate: " << result.error << std::endl;
return false;
}
if (options.format == Format::kSpvAsm) {
if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) {
return false;
}
} else {
if (!WriteFile(options.output_file, "wb", result.spirv)) {
return false;
}
}
const auto hash = tint::utils::CRC32(result.spirv.data(), result.spirv.size());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
// Use Vulkan 1.1, since this is what Tint, internally, uses.
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
tools.SetMessageConsumer(
[](spv_message_level_t, const char*, const spv_position_t& pos, const char* msg) {
std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg << std::endl;
});
if (!tools.Validate(result.spirv.data(), result.spirv.size(),
spvtools::ValidatorOptions())) {
return false;
}
}
return true;
#else
(void)program;
(void)options;
std::cerr << "SPIR-V writer not enabled in tint build" << std::endl;
return false;
#endif // TINT_BUILD_SPV_WRITER
}
/// Generate WGSL code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool GenerateWgsl(const tint::Program* program, const Options& options) {
#if TINT_BUILD_WGSL_WRITER
// TODO(jrprice): Provide a way for the user to set non-default options.
tint::writer::wgsl::Options gen_options;
auto result = tint::writer::wgsl::Generate(program, gen_options);
if (!result.success) {
std::cerr << "Failed to generate: " << result.error << std::endl;
return false;
}
if (!WriteFile(options.output_file, "w", result.wgsl)) {
return false;
}
const auto hash = tint::utils::CRC32(result.wgsl.data(), result.wgsl.size());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
// Attempt to re-parse the output program with Tint's WGSL reader.
auto source = std::make_unique<tint::Source::File>(options.input_filename, result.wgsl);
auto reparsed_program = tint::reader::wgsl::Parse(source.get());
if (!reparsed_program.IsValid()) {
auto diag_printer = tint::diag::Printer::create(stderr, true);
tint::diag::Formatter diag_formatter;
diag_formatter.format(reparsed_program.Diagnostics(), diag_printer.get());
return false;
}
}
return true;
#else
(void)program;
(void)options;
std::cerr << "WGSL writer not enabled in tint build" << std::endl;
return false;
#endif // TINT_BUILD_WGSL_WRITER
}
/// Generate MSL code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool GenerateMsl(const tint::Program* program, const Options& options) {
#if TINT_BUILD_MSL_WRITER
// Remap resource numbers to a flat namespace.
// TODO(crbug.com/tint/1501): Do this via Options::BindingMap.
const tint::Program* input_program = program;
auto flattened = tint::writer::FlattenBindings(program);
if (flattened) {
input_program = &*flattened;
}
// TODO(jrprice): Provide a way for the user to set non-default options.
tint::writer::msl::Options gen_options;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.generate_external_texture_bindings = true;
auto result = tint::writer::msl::Generate(input_program, gen_options);
if (!result.success) {
PrintWGSL(std::cerr, *program);
std::cerr << "Failed to generate: " << result.error << std::endl;
return false;
}
if (!WriteFile(options.output_file, "w", result.msl)) {
return false;
}
const auto hash = tint::utils::CRC32(result.msl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
tint::val::Result res;
#ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
res = tint::val::MslUsingMetalAPI(result.msl);
#else
#ifdef _WIN32
const char* default_xcrun_exe = "metal.exe";
#else
const char* default_xcrun_exe = "xcrun";
#endif
auto xcrun = tint::utils::Command::LookPath(
options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path);
if (xcrun.Found()) {
res = tint::val::Msl(xcrun.Path(), result.msl);
} else {
res.output = "xcrun executable not found. Cannot validate.";
res.failed = true;
}
#endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
if (res.failed) {
std::cerr << res.output << std::endl;
return false;
}
}
return true;
#else
(void)program;
(void)options;
std::cerr << "MSL writer not enabled in tint build" << std::endl;
return false;
#endif // TINT_BUILD_MSL_WRITER
}
/// Generate HLSL code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool GenerateHlsl(const tint::Program* program, const Options& options) {
#if TINT_BUILD_HLSL_WRITER
// TODO(jrprice): Provide a way for the user to set non-default options.
tint::writer::hlsl::Options gen_options;
gen_options.disable_workgroup_init = options.disable_workgroup_init;
gen_options.generate_external_texture_bindings = true;
gen_options.root_constant_binding_point = options.hlsl_root_constant_binding_point;
auto result = tint::writer::hlsl::Generate(program, gen_options);
if (!result.success) {
PrintWGSL(std::cerr, *program);
std::cerr << "Failed to generate: " << result.error << std::endl;
return false;
}
if (!WriteFile(options.output_file, "w", result.hlsl)) {
return false;
}
const auto hash = tint::utils::CRC32(result.hlsl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
// If --fxc or --dxc was passed, then we must explicitly find and validate with that respective
// compiler.
const bool must_validate_dxc = !options.dxc_path.empty();
const bool must_validate_fxc = !options.fxc_path.empty();
if ((options.validate || must_validate_dxc || must_validate_fxc) &&
(options.skip_hash.count(hash) == 0)) {
tint::val::Result dxc_res;
bool dxc_found = false;
if (options.validate || must_validate_dxc) {
auto dxc =
tint::utils::Command::LookPath(options.dxc_path.empty() ? "dxc" : options.dxc_path);
if (dxc.Found()) {
dxc_found = true;
auto enable_list = program->AST().Enables();
bool dxc_require_16bit_types = false;
for (auto enable : enable_list) {
if (enable->extension == tint::ast::Extension::kF16) {
dxc_require_16bit_types = true;
break;
}
}
dxc_res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl, result.entry_points,
dxc_require_16bit_types);
} else if (must_validate_dxc) {
// DXC was explicitly requested. Error if it could not be found.
dxc_res.failed = true;
dxc_res.output =
"DXC executable '" + options.dxc_path + "' not found. Cannot validate";
}
}
tint::val::Result fxc_res;
bool fxc_found = false;
if (options.validate || must_validate_fxc) {
auto fxc = tint::utils::Command::LookPath(
options.fxc_path.empty() ? tint::val::kFxcDLLName : options.fxc_path);
#ifdef _WIN32
if (fxc.Found()) {
fxc_found = true;
fxc_res = tint::val::HlslUsingFXC(fxc.Path(), result.hlsl, result.entry_points);
} else if (must_validate_fxc) {
// FXC was explicitly requested. Error if it could not be found.
fxc_res.failed = true;
fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate";
}
#else
if (must_validate_dxc) {
fxc_res.failed = true;
fxc_res.output = "FXC can only be used on Windows.";
}
#endif // _WIN32
}
if (fxc_res.failed) {
std::cerr << "FXC validation failure:" << std::endl << fxc_res.output << std::endl;
}
if (dxc_res.failed) {
std::cerr << "DXC validation failure:" << std::endl << dxc_res.output << std::endl;
}
if (fxc_res.failed || dxc_res.failed) {
return false;
}
if (!fxc_found && !dxc_found) {
std::cerr << "Couldn't find FXC or DXC. Cannot validate" << std::endl;
return false;
}
if (options.verbose) {
if (fxc_found && !fxc_res.failed) {
std::cout << "Passed FXC validation" << std::endl;
std::cout << fxc_res.output;
std::cout << std::endl;
}
if (dxc_found && !dxc_res.failed) {
std::cout << "Passed DXC validation" << std::endl;
std::cout << dxc_res.output;
std::cout << std::endl;
}
}
}
return true;
#else
(void)program;
(void)options;
std::cerr << "HLSL writer not enabled in tint build" << std::endl;
return false;
#endif // TINT_BUILD_HLSL_WRITER
}
#if TINT_BUILD_GLSL_WRITER
EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) {
switch (stage) {
case tint::ast::PipelineStage::kFragment:
return EShLangFragment;
case tint::ast::PipelineStage::kVertex:
return EShLangVertex;
case tint::ast::PipelineStage::kCompute:
return EShLangCompute;
default:
TINT_ASSERT(AST, false);
return EShLangVertex;
}
}
#endif
/// Generate GLSL code for a program.
/// @param program the program to generate
/// @param options the options that Tint was invoked with
/// @returns true on success
bool GenerateGlsl(const tint::Program* program, const Options& options) {
#if TINT_BUILD_GLSL_WRITER
if (options.validate) {
glslang::InitializeProcess();
}
auto generate = [&](const tint::Program* prg, const std::string entry_point_name) -> bool {
tint::writer::glsl::Options gen_options;
gen_options.generate_external_texture_bindings = true;
auto result = tint::writer::glsl::Generate(prg, gen_options, entry_point_name);
if (!result.success) {
PrintWGSL(std::cerr, *prg);
std::cerr << "Failed to generate: " << result.error << std::endl;
return false;
}
if (!WriteFile(options.output_file, "w", result.glsl)) {
return false;
}
const auto hash = tint::utils::CRC32(result.glsl.c_str());
if (options.print_hash) {
PrintHash(hash);
}
if (options.validate && options.skip_hash.count(hash) == 0) {
for (auto entry_pt : result.entry_points) {
EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second);
glslang::TShader shader(lang);
const char* strings[1] = {result.glsl.c_str()};
int lengths[1] = {static_cast<int>(result.glsl.length())};
shader.setStringsWithLengths(strings, lengths, 1);
shader.setEntryPoint("main");
bool glslang_result = shader.parse(GetDefaultResources(), 310, EEsProfile, false,
false, EShMsgDefault);
if (!glslang_result) {
std::cerr << "Error parsing GLSL shader:\n"
<< shader.getInfoLog() << "\n"
<< shader.getInfoDebugLog() << "\n";
return false;
}
}
}
return true;
};
tint::inspector::Inspector inspector(program);
if (inspector.GetEntryPoints().empty()) {
// Pass empty string here so that the GLSL generator will generate
// code for all functions, reachable or not.
return generate(program, "");
}
bool success = true;
for (auto& entry_point : inspector.GetEntryPoints()) {
success &= generate(program, entry_point.name);
}
return success;
#else
(void)program;
(void)options;
std::cerr << "GLSL writer not enabled in tint build" << std::endl;
return false;
#endif // TINT_BUILD_GLSL_WRITER
}
} // namespace
int main(int argc, const char** argv) {
std::vector<std::string> args(argv, argv + argc);
Options options;
tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
#if TINT_BUILD_WGSL_WRITER
tint::Program::printer = [](const tint::Program* program) {
auto result = tint::writer::wgsl::Generate(program, {});
if (!result.error.empty()) {
return "error: " + result.error;
}
return result.wgsl;
};
#endif // TINT_BUILD_WGSL_WRITER
if (!ParseArgs(args, &options)) {
std::cerr << "Failed to parse arguments." << std::endl;
return 1;
}
struct TransformFactory {
const char* name;
/// Build and adds the transform to the transform manager.
/// Parameters:
/// inspector - an inspector created from the parsed program
/// manager - the transform manager. Add transforms to this.
/// inputs - the input data to the transform manager. Add inputs to this.
/// Returns true on success, false on error (program will immediately exit)
std::function<bool(tint::inspector::Inspector& inspector,
tint::transform::Manager& manager,
tint::transform::DataMap& inputs)>
make;
};
std::vector<TransformFactory> transforms = {
{"first_index_offset",
[](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap& i) {
i.Add<tint::transform::FirstIndexOffset::BindingPoint>(0, 0);
m.Add<tint::transform::FirstIndexOffset>();
return true;
}},
{"renamer",
[](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap&) {
m.Add<tint::transform::Renamer>();
return true;
}},
{"robustness",
[](tint::inspector::Inspector&, tint::transform::Manager& m, tint::transform::DataMap&) {
m.Add<tint::transform::Robustness>();
return true;
}},
{"substitute_override",
[&](tint::inspector::Inspector& inspector, tint::transform::Manager& m,
tint::transform::DataMap& i) {
tint::transform::SubstituteOverride::Config cfg;
std::unordered_map<tint::OverrideId, double> values;
values.reserve(options.overrides.size());
for (const auto& [name, value] : options.overrides) {
if (name.empty()) {
std::cerr << "empty override name";
return false;
}
if (isdigit(name[0])) {
tint::OverrideId id{
static_cast<decltype(tint::OverrideId::value)>(atoi(name.c_str()))};
values.emplace(id, value);
} else {
auto override_names = inspector.GetNamedOverrideIds();
auto it = override_names.find(name);
if (it == override_names.end()) {
std::cerr << "unknown override '" << name << "'";
return false;
}
values.emplace(it->second, value);
}
}
cfg.map = std::move(values);
i.Add<tint::transform::SubstituteOverride::Config>(cfg);
m.Add<tint::transform::SubstituteOverride>();
return true;
}},
{"multiplaner_external_texture",
[](tint::inspector::Inspector& inspector, tint::transform::Manager& m,
tint::transform::DataMap& i) {
using MET = tint::transform::MultiplanarExternalTexture;
// Generate the MultiplanarExternalTexture::NewBindingPoints by finding two free
// binding points. We may wish to expose these binding points via a command line flag
// in the future.
// Set of all the group-0 bindings in use.
std::unordered_set<uint32_t> group0_bindings_in_use;
auto allocate_binding = [&] {
for (uint32_t idx = 0;; idx++) {
auto binding = tint::transform::BindingPoint{0u, idx};
if (group0_bindings_in_use.emplace(idx).second) {
return binding;
}
}
};
// Populate group0_bindings_in_use with the existing bindings across all entry points.
for (auto ep : inspector.GetEntryPoints()) {
for (auto binding : inspector.GetResourceBindings(ep.name)) {
if (binding.bind_group == 0) {
group0_bindings_in_use.emplace(binding.binding);
}
}
}
// Allocate new binding points for the external texture's planes and parameters.
MET::BindingsMap met_bindings;
for (auto ep : inspector.GetEntryPoints()) {
for (auto ext_tex : inspector.GetExternalTextureResourceBindings(ep.name)) {
auto binding = tint::transform::BindingPoint{
ext_tex.bind_group,
ext_tex.binding,
};
if (met_bindings.count(binding)) {
continue;
}
met_bindings.emplace(binding, MET::BindingPoints{
/* plane_1 */ allocate_binding(),
/* params */ allocate_binding(),
});
}
}
i.Add<MET::NewBindingPoints>(std::move(met_bindings));
m.Add<MET>();
return true;
}},
};
auto transform_names = [&] {
std::stringstream names;
for (auto& t : transforms) {
names << " " << t.name << std::endl;
}
return names.str();
};
if (options.show_help) {
std::string usage = tint::utils::ReplaceAll(kUsage, "${transforms}", transform_names());
#if TINT_BUILD_IR
usage +=
" --dump-ir -- Writes the IR to stdout\n"
" --dump-ir-graph -- Writes the IR graph to 'tint.dot' as a dot graph\n";
#endif // TINT_BUILD_IR
std::cout << usage << std::endl;
return 0;
}
// Implement output format defaults.
if (options.format == Format::kUnknown) {
// Try inferring from filename.
options.format = infer_format(options.output_file);
}
if (options.format == Format::kUnknown) {
// Ultimately, default to SPIR-V assembly. That's nice for interactive use.
options.format = Format::kSpvAsm;
}
auto diag_printer = tint::diag::Printer::create(stderr, true);
tint::diag::Formatter diag_formatter;
std::unique_ptr<tint::Program> program;
std::unique_ptr<tint::Source::File> source_file;
enum class InputFormat {
kUnknown,
kWgsl,
kSpirvBin,
kSpirvAsm,
};
auto input_format = InputFormat::kUnknown;
if (options.input_filename.size() > 5 &&
options.input_filename.substr(options.input_filename.size() - 5) == ".wgsl") {
input_format = InputFormat::kWgsl;
} else if (options.input_filename.size() > 4 &&
options.input_filename.substr(options.input_filename.size() - 4) == ".spv") {
input_format = InputFormat::kSpirvBin;
} else if (options.input_filename.size() > 7 &&
options.input_filename.substr(options.input_filename.size() - 7) == ".spvasm") {
input_format = InputFormat::kSpirvAsm;
}
switch (input_format) {
case InputFormat::kUnknown: {
std::cerr << "Unknown input format" << std::endl;
return 1;
}
case InputFormat::kWgsl: {
#if TINT_BUILD_WGSL_READER
std::vector<uint8_t> data;
if (!ReadFile<uint8_t>(options.input_filename, &data)) {
return 1;
}
source_file = std::make_unique<tint::Source::File>(
options.input_filename, std::string(data.begin(), data.end()));
program = std::make_unique<tint::Program>(tint::reader::wgsl::Parse(source_file.get()));
break;
#else
std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
return 1;
#endif // TINT_BUILD_WGSL_READER
}
case InputFormat::kSpirvBin: {
#if TINT_BUILD_SPV_READER
std::vector<uint32_t> data;
if (!ReadFile<uint32_t>(options.input_filename, &data)) {
return 1;
}
program = std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
break;
#else
std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
return 1;
#endif // TINT_BUILD_SPV_READER
}
case InputFormat::kSpirvAsm: {
#if TINT_BUILD_SPV_READER
std::vector<char> text;
if (!ReadFile<char>(options.input_filename, &text)) {
return 1;
}
// Use Vulkan 1.1, since this is what Tint, internally, is expecting.
spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& pos,
const char* msg) {
std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg << std::endl;
});
std::vector<uint32_t> data;
if (!tools.Assemble(text.data(), text.size(), &data,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
return 1;
}
program = std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
break;
#else
std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
return 1;
#endif // TINT_BUILD_SPV_READER
}
}
if (!program) {
std::cerr << "Failed to parse input file: " << options.input_filename << std::endl;
return 1;
}
if (program->Diagnostics().count() > 0) {
if (!program->IsValid() && input_format != InputFormat::kWgsl) {
// Invalid program from a non-wgsl source. Print the WGSL, to help
// understand the diagnostics.
PrintWGSL(std::cout, *program);
}
diag_formatter.format(program->Diagnostics(), diag_printer.get());
}
if (!program->IsValid()) {
return 1;
}
if (options.parse_only) {
return 1;
}
#if TINT_BUILD_IR
if (options.dump_ir || options.dump_ir_graph) {
auto result = tint::ir::Module::FromProgram(program.get());
if (!result) {
std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
} else {
auto mod = result.Move();
if (options.dump_ir) {
tint::ir::Disassembler d(mod);
std::cout << d.Disassemble() << std::endl;
}
if (options.dump_ir_graph) {
auto graph = tint::ir::Debug::AsDotGraph(&mod);
WriteFile("tint.dot", "w", graph);
}
}
}
#endif // TINT_BUILD_IR
tint::inspector::Inspector inspector(program.get());
if (options.dump_inspector_bindings) {
std::cout << std::string(80, '-') << std::endl;
auto entry_points = inspector.GetEntryPoints();
if (!inspector.error().empty()) {
std::cerr << "Failed to get entry points from Inspector: " << inspector.error()
<< std::endl;
return 1;
}
for (auto& entry_point : entry_points) {
auto bindings = inspector.GetResourceBindings(entry_point.name);
if (!inspector.error().empty()) {
std::cerr << "Failed to get bindings from Inspector: " << inspector.error()
<< std::endl;
return 1;
}
std::cout << "Entry Point = " << entry_point.name << std::endl;
for (auto& binding : bindings) {
std::cout << "\t[" << binding.bind_group << "][" << binding.binding
<< "]:" << std::endl;
std::cout << "\t\t resource_type = " << ResourceTypeToString(binding.resource_type)
<< std::endl;
std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim) << std::endl;
std::cout << "\t\t sampled_kind = " << SampledKindToString(binding.sampled_kind)
<< std::endl;
std::cout << "\t\t image_format = " << TexelFormatToString(binding.image_format)
<< std::endl;
}
}
std::cout << std::string(80, '-') << std::endl;
}
tint::transform::Manager transform_manager;
tint::transform::DataMap transform_inputs;
// Renaming must always come first
switch (options.format) {
case Format::kMsl: {
#if TINT_BUILD_MSL_WRITER
transform_inputs.Add<tint::transform::Renamer::Config>(
options.rename_all ? tint::transform::Renamer::Target::kAll
: tint::transform::Renamer::Target::kMslKeywords,
/* preserve_unicode */ false);
transform_manager.Add<tint::transform::Renamer>();
#endif // TINT_BUILD_MSL_WRITER
break;
}
#if TINT_BUILD_GLSL_WRITER
case Format::kGlsl: {
transform_inputs.Add<tint::transform::Renamer::Config>(
options.rename_all ? tint::transform::Renamer::Target::kAll
: tint::transform::Renamer::Target::kGlslKeywords,
/* preserve_unicode */ false);
transform_manager.Add<tint::transform::Renamer>();
break;
}
#endif // TINT_BUILD_GLSL_WRITER
case Format::kHlsl: {
#if TINT_BUILD_HLSL_WRITER
transform_inputs.Add<tint::transform::Renamer::Config>(
options.rename_all ? tint::transform::Renamer::Target::kAll
: tint::transform::Renamer::Target::kHlslKeywords,
/* preserve_unicode */ false);
transform_manager.Add<tint::transform::Renamer>();
#endif // TINT_BUILD_HLSL_WRITER
break;
}
default: {
if (options.rename_all) {
transform_manager.Add<tint::transform::Renamer>();
}
break;
}
}
auto enable_transform = [&](std::string_view name) {
for (auto& t : transforms) {
if (t.name == name) {
return t.make(inspector, transform_manager, transform_inputs);
}
}
std::cerr << "Unknown transform: " << name << std::endl;
std::cerr << "Available transforms: " << std::endl << transform_names();
return false;
};
// If overrides are provided, add the SubstituteOverride transform.
if (!options.overrides.empty()) {
if (!enable_transform("substitute_override")) {
return 1;
}
}
for (const auto& name : options.transforms) {
// TODO(dsinclair): The vertex pulling transform requires setup code to
// be run that needs user input. Should we find a way to support that here
// maybe through a provided file?
if (!enable_transform(name)) {
return 1;
}
}
if (options.emit_single_entry_point) {
transform_manager.append(std::make_unique<tint::transform::SingleEntryPoint>());
transform_inputs.Add<tint::transform::SingleEntryPoint::Config>(options.ep_name);
}
auto out = transform_manager.Run(program.get(), std::move(transform_inputs));
if (!out.program.IsValid()) {
PrintWGSL(std::cerr, out.program);
diag_formatter.format(out.program.Diagnostics(), diag_printer.get());
return 1;
}
*program = std::move(out.program);
bool success = false;
switch (options.format) {
case Format::kSpirv:
case Format::kSpvAsm:
success = GenerateSpirv(program.get(), options);
break;
case Format::kWgsl:
success = GenerateWgsl(program.get(), options);
break;
case Format::kMsl:
success = GenerateMsl(program.get(), options);
break;
case Format::kHlsl:
success = GenerateHlsl(program.get(), options);
break;
case Format::kGlsl:
success = GenerateGlsl(program.get(), options);
break;
case Format::kNone:
break;
default:
std::cerr << "Unknown output format specified" << std::endl;
return 1;
}
if (!success) {
return 1;
}
return 0;
}