1 // Copyright 2020 The Tint Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <cstdio>
16 #include <fstream>
17 #include <iostream>
18 #include <memory>
19 #include <sstream>
20 #include <string>
21 #include <vector>
22
23 #if TINT_BUILD_GLSL_WRITER
24 #include "StandAlone/ResourceLimits.h"
25 #include "glslang/Public/ShaderLang.h"
26 #endif
27
28 #if TINT_BUILD_SPV_READER
29 #include "spirv-tools/libspirv.hpp"
30 #endif // TINT_BUILD_SPV_READER
31
32 #include "src/utils/io/command.h"
33 #include "src/val/val.h"
34 #include "tint/tint.h"
35
36 namespace {
37
TintInternalCompilerErrorReporter(const tint::diag::List & diagnostics)38 [[noreturn]] void TintInternalCompilerErrorReporter(
39 const tint::diag::List& diagnostics) {
40 auto printer = tint::diag::Printer::create(stderr, true);
41 tint::diag::Formatter{}.format(diagnostics, printer.get());
42 tint::diag::Style bold_red{tint::diag::Color::kRed, true};
43 constexpr const char* please_file_bug = R"(
44 ********************************************************************
45 * The tint shader compiler has encountered an unexpected error. *
46 * *
47 * Please help us fix this issue by submitting a bug report at *
48 * crbug.com/tint with the source program that triggered the bug. *
49 ********************************************************************
50 )";
51 printer->write(please_file_bug, bold_red);
52 exit(1);
53 }
54
55 enum class Format {
56 kNone = -1,
57 kSpirv,
58 kSpvAsm,
59 kWgsl,
60 kMsl,
61 kHlsl,
62 kGlsl,
63 };
64
65 struct Options {
66 bool show_help = false;
67
68 std::string input_filename;
69 std::string output_file = "-"; // Default to stdout
70
71 bool parse_only = false;
72 bool disable_workgroup_init = false;
73 bool validate = false;
74 bool demangle = false;
75 bool dump_inspector_bindings = false;
76
77 Format format = Format::kNone;
78
79 bool emit_single_entry_point = false;
80 std::string ep_name;
81
82 std::vector<std::string> transforms;
83
84 bool use_fxc = false;
85 std::string dxc_path;
86 std::string xcrun_path;
87 };
88
89 const char kUsage[] = R"(Usage: tint [options] <input-file>
90
91 options:
92 --format <spirv|spvasm|wgsl|msl|hlsl> -- Output format.
93 If not provided, will be inferred from output
94 filename extension:
95 .spvasm -> spvasm
96 .spv -> spirv
97 .wgsl -> wgsl
98 .metal -> msl
99 .hlsl -> hlsl
100 If none matches, then default to SPIR-V assembly.
101 -ep <name> -- Output single entry point
102 --output-file <name> -- Output file name. Use "-" for standard output
103 -o <name> -- Output file name. Use "-" for standard output
104 --transform <name list> -- Runs transforms, name list is comma separated
105 Available transforms:
106 first_index_offset
107 fold_trivial_single_use_lets
108 renamer
109 robustness
110 --parse-only -- Stop after parsing the input
111 --disable-workgroup-init -- Disable workgroup memory zero initialization.
112 --demangle -- Preserve original source names. Demangle them.
113 Affects AST dumping, and text-based output languages.
114 --dump-inspector-bindings -- Dump reflection data about bindins to stdout.
115 -h -- This help text
116 --validate -- Validates the generated shader
117 --fxc -- Ask to validate HLSL output using FXC instead of DXC.
118 When specified, automatically enables --validate
119 --dxc -- Path to DXC executable, used to validate HLSL output.
120 When specified, automatically enables --validate
121 --xcrun -- Path to xcrun executable, used to validate MSL output.
122 When specified, automatically enables --validate)";
123
parse_format(const std::string & fmt)124 Format parse_format(const std::string& fmt) {
125 (void)fmt;
126
127 #if TINT_BUILD_SPV_WRITER
128 if (fmt == "spirv")
129 return Format::kSpirv;
130 if (fmt == "spvasm")
131 return Format::kSpvAsm;
132 #endif // TINT_BUILD_SPV_WRITER
133
134 #if TINT_BUILD_WGSL_WRITER
135 if (fmt == "wgsl")
136 return Format::kWgsl;
137 #endif // TINT_BUILD_WGSL_WRITER
138
139 #if TINT_BUILD_MSL_WRITER
140 if (fmt == "msl")
141 return Format::kMsl;
142 #endif // TINT_BUILD_MSL_WRITER
143
144 #if TINT_BUILD_HLSL_WRITER
145 if (fmt == "hlsl")
146 return Format::kHlsl;
147 #endif // TINT_BUILD_HLSL_WRITER
148
149 #if TINT_BUILD_GLSL_WRITER
150 if (fmt == "glsl")
151 return Format::kGlsl;
152 #endif // TINT_BUILD_GLSL_WRITER
153
154 return Format::kNone;
155 }
156
157 #if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || \
158 TINT_BUILD_MSL_WRITER || TINT_BUILD_HLSL_WRITER
159 /// @param input input string
160 /// @param suffix potential suffix string
161 /// @returns true if input ends with the given suffix.
ends_with(const std::string & input,const std::string & suffix)162 bool ends_with(const std::string& input, const std::string& suffix) {
163 const auto input_len = input.size();
164 const auto suffix_len = suffix.size();
165 // Avoid integer overflow.
166 return (input_len >= suffix_len) &&
167 (input_len - suffix_len == input.rfind(suffix));
168 }
169 #endif
170
171 /// @param filename the filename to inspect
172 /// @returns the inferred format for the filename suffix
infer_format(const std::string & filename)173 Format infer_format(const std::string& filename) {
174 (void)filename;
175
176 #if TINT_BUILD_SPV_WRITER
177 if (ends_with(filename, ".spv")) {
178 return Format::kSpirv;
179 }
180 if (ends_with(filename, ".spvasm")) {
181 return Format::kSpvAsm;
182 }
183 #endif // TINT_BUILD_SPV_WRITER
184
185 #if TINT_BUILD_WGSL_WRITER
186 if (ends_with(filename, ".wgsl")) {
187 return Format::kWgsl;
188 }
189 #endif // TINT_BUILD_WGSL_WRITER
190
191 #if TINT_BUILD_MSL_WRITER
192 if (ends_with(filename, ".metal")) {
193 return Format::kMsl;
194 }
195 #endif // TINT_BUILD_MSL_WRITER
196
197 #if TINT_BUILD_HLSL_WRITER
198 if (ends_with(filename, ".hlsl")) {
199 return Format::kHlsl;
200 }
201 #endif // TINT_BUILD_HLSL_WRITER
202
203 return Format::kNone;
204 }
205
split_transform_names(std::string list)206 std::vector<std::string> split_transform_names(std::string list) {
207 std::vector<std::string> res;
208
209 std::stringstream str(list);
210 while (str.good()) {
211 std::string substr;
212 getline(str, substr, ',');
213 res.push_back(substr);
214 }
215 return res;
216 }
217
TextureDimensionToString(tint::inspector::ResourceBinding::TextureDimension dim)218 std::string TextureDimensionToString(
219 tint::inspector::ResourceBinding::TextureDimension dim) {
220 switch (dim) {
221 case tint::inspector::ResourceBinding::TextureDimension::kNone:
222 return "None";
223 case tint::inspector::ResourceBinding::TextureDimension::k1d:
224 return "1d";
225 case tint::inspector::ResourceBinding::TextureDimension::k2d:
226 return "2d";
227 case tint::inspector::ResourceBinding::TextureDimension::k2dArray:
228 return "2dArray";
229 case tint::inspector::ResourceBinding::TextureDimension::k3d:
230 return "3d";
231 case tint::inspector::ResourceBinding::TextureDimension::kCube:
232 return "Cube";
233 case tint::inspector::ResourceBinding::TextureDimension::kCubeArray:
234 return "CubeArray";
235 }
236
237 return "Unknown";
238 }
239
SampledKindToString(tint::inspector::ResourceBinding::SampledKind kind)240 std::string SampledKindToString(
241 tint::inspector::ResourceBinding::SampledKind kind) {
242 switch (kind) {
243 case tint::inspector::ResourceBinding::SampledKind::kFloat:
244 return "Float";
245 case tint::inspector::ResourceBinding::SampledKind::kUInt:
246 return "UInt";
247 case tint::inspector::ResourceBinding::SampledKind::kSInt:
248 return "SInt";
249 case tint::inspector::ResourceBinding::SampledKind::kUnknown:
250 break;
251 }
252
253 return "Unknown";
254 }
255
ImageFormatToString(tint::inspector::ResourceBinding::ImageFormat format)256 std::string ImageFormatToString(
257 tint::inspector::ResourceBinding::ImageFormat format) {
258 switch (format) {
259 case tint::inspector::ResourceBinding::ImageFormat::kR8Unorm:
260 return "R8Unorm";
261 case tint::inspector::ResourceBinding::ImageFormat::kR8Snorm:
262 return "R8Snorm";
263 case tint::inspector::ResourceBinding::ImageFormat::kR8Uint:
264 return "R8Uint";
265 case tint::inspector::ResourceBinding::ImageFormat::kR8Sint:
266 return "R8Sint";
267 case tint::inspector::ResourceBinding::ImageFormat::kR16Uint:
268 return "R16Uint";
269 case tint::inspector::ResourceBinding::ImageFormat::kR16Sint:
270 return "R16Sint";
271 case tint::inspector::ResourceBinding::ImageFormat::kR16Float:
272 return "R16Float";
273 case tint::inspector::ResourceBinding::ImageFormat::kRg8Unorm:
274 return "Rg8Unorm";
275 case tint::inspector::ResourceBinding::ImageFormat::kRg8Snorm:
276 return "Rg8Snorm";
277 case tint::inspector::ResourceBinding::ImageFormat::kRg8Uint:
278 return "Rg8Uint";
279 case tint::inspector::ResourceBinding::ImageFormat::kRg8Sint:
280 return "Rg8Sint";
281 case tint::inspector::ResourceBinding::ImageFormat::kR32Uint:
282 return "R32Uint";
283 case tint::inspector::ResourceBinding::ImageFormat::kR32Sint:
284 return "R32Sint";
285 case tint::inspector::ResourceBinding::ImageFormat::kR32Float:
286 return "R32Float";
287 case tint::inspector::ResourceBinding::ImageFormat::kRg16Uint:
288 return "Rg16Uint";
289 case tint::inspector::ResourceBinding::ImageFormat::kRg16Sint:
290 return "Rg16Sint";
291 case tint::inspector::ResourceBinding::ImageFormat::kRg16Float:
292 return "Rg16Float";
293 case tint::inspector::ResourceBinding::ImageFormat::kRgba8Unorm:
294 return "Rgba8Unorm";
295 case tint::inspector::ResourceBinding::ImageFormat::kRgba8UnormSrgb:
296 return "Rgba8UnormSrgb";
297 case tint::inspector::ResourceBinding::ImageFormat::kRgba8Snorm:
298 return "Rgba8Snorm";
299 case tint::inspector::ResourceBinding::ImageFormat::kRgba8Uint:
300 return "Rgba8Uint";
301 case tint::inspector::ResourceBinding::ImageFormat::kRgba8Sint:
302 return "Rgba8Sint";
303 case tint::inspector::ResourceBinding::ImageFormat::kBgra8Unorm:
304 return "Bgra8Unorm";
305 case tint::inspector::ResourceBinding::ImageFormat::kBgra8UnormSrgb:
306 return "Bgra8UnormSrgb";
307 case tint::inspector::ResourceBinding::ImageFormat::kRgb10A2Unorm:
308 return "Rgb10A2Unorm";
309 case tint::inspector::ResourceBinding::ImageFormat::kRg11B10Float:
310 return "Rg11B10Float";
311 case tint::inspector::ResourceBinding::ImageFormat::kRg32Uint:
312 return "Rg32Uint";
313 case tint::inspector::ResourceBinding::ImageFormat::kRg32Sint:
314 return "Rg32Sint";
315 case tint::inspector::ResourceBinding::ImageFormat::kRg32Float:
316 return "Rg32Float";
317 case tint::inspector::ResourceBinding::ImageFormat::kRgba16Uint:
318 return "Rgba16Uint";
319 case tint::inspector::ResourceBinding::ImageFormat::kRgba16Sint:
320 return "Rgba16Sint";
321 case tint::inspector::ResourceBinding::ImageFormat::kRgba16Float:
322 return "Rgba16Float";
323 case tint::inspector::ResourceBinding::ImageFormat::kRgba32Uint:
324 return "Rgba32Uint";
325 case tint::inspector::ResourceBinding::ImageFormat::kRgba32Sint:
326 return "Rgba32Sint";
327 case tint::inspector::ResourceBinding::ImageFormat::kRgba32Float:
328 return "Rgba32Float";
329 case tint::inspector::ResourceBinding::ImageFormat::kNone:
330 return "None";
331 }
332 return "Unknown";
333 }
334
ResourceTypeToString(tint::inspector::ResourceBinding::ResourceType type)335 std::string ResourceTypeToString(
336 tint::inspector::ResourceBinding::ResourceType type) {
337 switch (type) {
338 case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
339 return "UniformBuffer";
340 case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
341 return "StorageBuffer";
342 case tint::inspector::ResourceBinding::ResourceType::kReadOnlyStorageBuffer:
343 return "ReadOnlyStorageBuffer";
344 case tint::inspector::ResourceBinding::ResourceType::kSampler:
345 return "Sampler";
346 case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
347 return "ComparisonSampler";
348 case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
349 return "SampledTexture";
350 case tint::inspector::ResourceBinding::ResourceType::kMultisampledTexture:
351 return "MultisampledTexture";
352 case tint::inspector::ResourceBinding::ResourceType::
353 kWriteOnlyStorageTexture:
354 return "WriteOnlyStorageTexture";
355 case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
356 return "DepthTexture";
357 case tint::inspector::ResourceBinding::ResourceType::
358 kDepthMultisampledTexture:
359 return "DepthMultisampledTexture";
360 case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
361 return "ExternalTexture";
362 }
363
364 return "Unknown";
365 }
366
ParseArgs(const std::vector<std::string> & args,Options * opts)367 bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
368 for (size_t i = 1; i < args.size(); ++i) {
369 const std::string& arg = args[i];
370 if (arg == "--format") {
371 ++i;
372 if (i >= args.size()) {
373 std::cerr << "Missing value for --format argument." << std::endl;
374 return false;
375 }
376 opts->format = parse_format(args[i]);
377
378 if (opts->format == Format::kNone) {
379 std::cerr << "Unknown output format: " << args[i] << std::endl;
380 return false;
381 }
382 } else if (arg == "-ep") {
383 if (i + 1 >= args.size()) {
384 std::cerr << "Missing value for -ep" << std::endl;
385 return false;
386 }
387 i++;
388 opts->ep_name = args[i];
389 opts->emit_single_entry_point = true;
390
391 } else if (arg == "-o" || arg == "--output-name") {
392 ++i;
393 if (i >= args.size()) {
394 std::cerr << "Missing value for " << arg << std::endl;
395 return false;
396 }
397 opts->output_file = args[i];
398
399 } else if (arg == "-h" || arg == "--help") {
400 opts->show_help = true;
401 } else if (arg == "--transform") {
402 ++i;
403 if (i >= args.size()) {
404 std::cerr << "Missing value for " << arg << std::endl;
405 return false;
406 }
407 opts->transforms = split_transform_names(args[i]);
408 } else if (arg == "--parse-only") {
409 opts->parse_only = true;
410 } else if (arg == "--disable-workgroup-init") {
411 opts->disable_workgroup_init = true;
412 } else if (arg == "--demangle") {
413 opts->demangle = true;
414 } else if (arg == "--dump-inspector-bindings") {
415 opts->dump_inspector_bindings = true;
416 } else if (arg == "--validate") {
417 opts->validate = true;
418 } else if (arg == "--fxc") {
419 opts->validate = true;
420 opts->use_fxc = true;
421 } else if (arg == "--dxc") {
422 ++i;
423 if (i >= args.size()) {
424 std::cerr << "Missing value for " << arg << std::endl;
425 return false;
426 }
427 opts->dxc_path = args[i];
428 opts->validate = true;
429 } else if (arg == "--xcrun") {
430 ++i;
431 if (i >= args.size()) {
432 std::cerr << "Missing value for " << arg << std::endl;
433 return false;
434 }
435 opts->xcrun_path = args[i];
436 opts->validate = true;
437 } else if (!arg.empty()) {
438 if (arg[0] == '-') {
439 std::cerr << "Unrecognized option: " << arg << std::endl;
440 return false;
441 }
442 if (!opts->input_filename.empty()) {
443 std::cerr << "More than one input file specified: '"
444 << opts->input_filename << "' and '" << arg << "'"
445 << std::endl;
446 return false;
447 }
448 opts->input_filename = arg;
449 }
450 }
451 return true;
452 }
453
454 /// Copies the content from the file named `input_file` to `buffer`,
455 /// assuming each element in the file is of type `T`. If any error occurs,
456 /// writes error messages to the standard error stream and returns false.
457 /// Assumes the size of a `T` object is divisible by its required alignment.
458 /// @returns true if we successfully read the file.
459 template <typename T>
ReadFile(const std::string & input_file,std::vector<T> * buffer)460 bool ReadFile(const std::string& input_file, std::vector<T>* buffer) {
461 if (!buffer) {
462 std::cerr << "The buffer pointer was null" << std::endl;
463 return false;
464 }
465
466 FILE* file = nullptr;
467 #if defined(_MSC_VER)
468 fopen_s(&file, input_file.c_str(), "rb");
469 #else
470 file = fopen(input_file.c_str(), "rb");
471 #endif
472 if (!file) {
473 std::cerr << "Failed to open " << input_file << std::endl;
474 return false;
475 }
476
477 fseek(file, 0, SEEK_END);
478 uint64_t tell_file_size = static_cast<uint64_t>(ftell(file));
479 if (tell_file_size <= 0) {
480 std::cerr << "Input file of incorrect size: " << input_file << std::endl;
481 fclose(file);
482 return {};
483 }
484 const auto file_size = static_cast<size_t>(tell_file_size);
485 if (0 != (file_size % sizeof(T))) {
486 std::cerr << "File " << input_file
487 << " does not contain an integral number of objects: "
488 << file_size << " bytes in the file, require " << sizeof(T)
489 << " bytes per object" << std::endl;
490 fclose(file);
491 return false;
492 }
493 fseek(file, 0, SEEK_SET);
494
495 buffer->clear();
496 buffer->resize(file_size / sizeof(T));
497
498 size_t bytes_read = fread(buffer->data(), 1, file_size, file);
499 fclose(file);
500 if (bytes_read != file_size) {
501 std::cerr << "Failed to read " << input_file << std::endl;
502 return false;
503 }
504
505 return true;
506 }
507
508 /// Writes the given `buffer` into the file named as `output_file` using the
509 /// given `mode`. If `output_file` is empty or "-", writes to standard
510 /// output. If any error occurs, returns false and outputs error message to
511 /// standard error. The ContainerT type must have data() and size() methods,
512 /// like `std::string` and `std::vector` do.
513 /// @returns true on success
514 template <typename ContainerT>
WriteFile(const std::string & output_file,const std::string mode,const ContainerT & buffer)515 bool WriteFile(const std::string& output_file,
516 const std::string mode,
517 const ContainerT& buffer) {
518 const bool use_stdout = output_file.empty() || output_file == "-";
519 FILE* file = stdout;
520
521 if (!use_stdout) {
522 #if defined(_MSC_VER)
523 fopen_s(&file, output_file.c_str(), mode.c_str());
524 #else
525 file = fopen(output_file.c_str(), mode.c_str());
526 #endif
527 if (!file) {
528 std::cerr << "Could not open file " << output_file << " for writing"
529 << std::endl;
530 return false;
531 }
532 }
533
534 size_t written =
535 fwrite(buffer.data(), sizeof(typename ContainerT::value_type),
536 buffer.size(), file);
537 if (buffer.size() != written) {
538 if (use_stdout) {
539 std::cerr << "Could not write all output to standard output" << std::endl;
540 } else {
541 std::cerr << "Could not write to file " << output_file << std::endl;
542 fclose(file);
543 }
544 return false;
545 }
546 if (!use_stdout) {
547 fclose(file);
548 }
549
550 return true;
551 }
552
553 #if TINT_BUILD_SPV_WRITER
Disassemble(const std::vector<uint32_t> & data)554 std::string Disassemble(const std::vector<uint32_t>& data) {
555 std::string spv_errors;
556 spv_target_env target_env = SPV_ENV_UNIVERSAL_1_0;
557
558 auto msg_consumer = [&spv_errors](spv_message_level_t level, const char*,
559 const spv_position_t& position,
560 const char* message) {
561 switch (level) {
562 case SPV_MSG_FATAL:
563 case SPV_MSG_INTERNAL_ERROR:
564 case SPV_MSG_ERROR:
565 spv_errors += "error: line " + std::to_string(position.index) + ": " +
566 message + "\n";
567 break;
568 case SPV_MSG_WARNING:
569 spv_errors += "warning: line " + std::to_string(position.index) + ": " +
570 message + "\n";
571 break;
572 case SPV_MSG_INFO:
573 spv_errors += "info: line " + std::to_string(position.index) + ": " +
574 message + "\n";
575 break;
576 case SPV_MSG_DEBUG:
577 break;
578 }
579 };
580
581 spvtools::SpirvTools tools(target_env);
582 tools.SetMessageConsumer(msg_consumer);
583
584 std::string result;
585 if (!tools.Disassemble(data, &result,
586 SPV_BINARY_TO_TEXT_OPTION_INDENT |
587 SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)) {
588 std::cerr << spv_errors << std::endl;
589 }
590 return result;
591 }
592 #endif // TINT_BUILD_SPV_WRITER
593
594 /// PrintWGSL writes the WGSL of the program to the provided ostream, if the
595 /// WGSL writer is enabled, otherwise it does nothing.
596 /// @param out the output stream to write the WGSL to
597 /// @param program the program
PrintWGSL(std::ostream & out,const tint::Program & program)598 void PrintWGSL(std::ostream& out, const tint::Program& program) {
599 #if TINT_BUILD_WGSL_WRITER
600 tint::writer::wgsl::Options options;
601 auto result = tint::writer::wgsl::Generate(&program, options);
602 out << std::endl << result.wgsl << std::endl;
603 #else
604 (void)out;
605 (void)program;
606 #endif
607 }
608
609 /// Generate SPIR-V code for a program.
610 /// @param program the program to generate
611 /// @param options the options that Tint was invoked with
612 /// @returns true on success
GenerateSpirv(const tint::Program * program,const Options & options)613 bool GenerateSpirv(const tint::Program* program, const Options& options) {
614 #if TINT_BUILD_SPV_WRITER
615 // TODO(jrprice): Provide a way for the user to set non-default options.
616 tint::writer::spirv::Options gen_options;
617 gen_options.disable_workgroup_init = options.disable_workgroup_init;
618 auto result = tint::writer::spirv::Generate(program, gen_options);
619 if (!result.success) {
620 PrintWGSL(std::cerr, *program);
621 std::cerr << "Failed to generate: " << result.error << std::endl;
622 return false;
623 }
624
625 if (options.format == Format::kSpvAsm) {
626 if (!WriteFile(options.output_file, "w", Disassemble(result.spirv))) {
627 return false;
628 }
629 } else {
630 if (!WriteFile(options.output_file, "wb", result.spirv)) {
631 return false;
632 }
633 }
634
635 if (options.validate) {
636 // Use Vulkan 1.1, since this is what Tint, internally, uses.
637 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
638 tools.SetMessageConsumer([](spv_message_level_t, const char*,
639 const spv_position_t& pos, const char* msg) {
640 std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
641 << std::endl;
642 });
643 if (!tools.Validate(result.spirv.data(), result.spirv.size(),
644 spvtools::ValidatorOptions())) {
645 return false;
646 }
647 }
648
649 return true;
650 #else
651 (void)program;
652 (void)options;
653 std::cerr << "SPIR-V writer not enabled in tint build" << std::endl;
654 return false;
655 #endif // TINT_BUILD_SPV_WRITER
656 }
657
658 /// Generate WGSL code for a program.
659 /// @param program the program to generate
660 /// @param options the options that Tint was invoked with
661 /// @returns true on success
GenerateWgsl(const tint::Program * program,const Options & options)662 bool GenerateWgsl(const tint::Program* program, const Options& options) {
663 #if TINT_BUILD_WGSL_WRITER
664 // TODO(jrprice): Provide a way for the user to set non-default options.
665 tint::writer::wgsl::Options gen_options;
666 auto result = tint::writer::wgsl::Generate(program, gen_options);
667 if (!result.success) {
668 std::cerr << "Failed to generate: " << result.error << std::endl;
669 return false;
670 }
671
672 return WriteFile(options.output_file, "w", result.wgsl);
673 #else
674 (void)program;
675 (void)options;
676 std::cerr << "WGSL writer not enabled in tint build" << std::endl;
677 return false;
678 #endif // TINT_BUILD_WGSL_WRITER
679 }
680
681 /// Generate MSL code for a program.
682 /// @param program the program to generate
683 /// @param options the options that Tint was invoked with
684 /// @returns true on success
GenerateMsl(const tint::Program * program,const Options & options)685 bool GenerateMsl(const tint::Program* program, const Options& options) {
686 #if TINT_BUILD_MSL_WRITER
687 const tint::Program* input_program = program;
688
689 // Remap resource numbers to a flat namespace.
690 // TODO(crbug.com/tint/1101): Make this more robust for multiple entry points.
691 using BindingPoint = tint::transform::BindingPoint;
692 tint::transform::BindingRemapper::BindingPoints binding_points;
693 uint32_t next_buffer_idx = 0;
694 uint32_t next_sampler_idx = 0;
695 uint32_t next_texture_idx = 0;
696
697 tint::inspector::Inspector inspector(program);
698 auto entry_points = inspector.GetEntryPoints();
699 for (auto& entry_point : entry_points) {
700 auto bindings = inspector.GetResourceBindings(entry_point.name);
701 for (auto& binding : bindings) {
702 BindingPoint src = {binding.bind_group, binding.binding};
703 if (binding_points.count(src)) {
704 continue;
705 }
706 switch (binding.resource_type) {
707 case tint::inspector::ResourceBinding::ResourceType::kUniformBuffer:
708 case tint::inspector::ResourceBinding::ResourceType::kStorageBuffer:
709 case tint::inspector::ResourceBinding::ResourceType::
710 kReadOnlyStorageBuffer:
711 binding_points.emplace(src, BindingPoint{0, next_buffer_idx++});
712 break;
713 case tint::inspector::ResourceBinding::ResourceType::kSampler:
714 case tint::inspector::ResourceBinding::ResourceType::kComparisonSampler:
715 binding_points.emplace(src, BindingPoint{0, next_sampler_idx++});
716 break;
717 case tint::inspector::ResourceBinding::ResourceType::kSampledTexture:
718 case tint::inspector::ResourceBinding::ResourceType::
719 kMultisampledTexture:
720 case tint::inspector::ResourceBinding::ResourceType::
721 kWriteOnlyStorageTexture:
722 case tint::inspector::ResourceBinding::ResourceType::kDepthTexture:
723 case tint::inspector::ResourceBinding::ResourceType::
724 kDepthMultisampledTexture:
725 case tint::inspector::ResourceBinding::ResourceType::kExternalTexture:
726 binding_points.emplace(src, BindingPoint{0, next_texture_idx++});
727 break;
728 }
729 }
730 }
731
732 // Run the binding remapper transform.
733 tint::transform::Output transform_output;
734 if (!binding_points.empty()) {
735 tint::transform::Manager manager;
736 tint::transform::DataMap inputs;
737 inputs.Add<tint::transform::BindingRemapper::Remappings>(
738 std::move(binding_points),
739 tint::transform::BindingRemapper::AccessControls{},
740 /* mayCollide */ true);
741 manager.Add<tint::transform::BindingRemapper>();
742 transform_output = manager.Run(program, inputs);
743 input_program = &transform_output.program;
744 }
745
746 // TODO(jrprice): Provide a way for the user to set non-default options.
747 tint::writer::msl::Options gen_options;
748 gen_options.disable_workgroup_init = options.disable_workgroup_init;
749 auto result = tint::writer::msl::Generate(input_program, gen_options);
750 if (!result.success) {
751 PrintWGSL(std::cerr, *program);
752 std::cerr << "Failed to generate: " << result.error << std::endl;
753 return false;
754 }
755
756 if (!WriteFile(options.output_file, "w", result.msl)) {
757 return false;
758 }
759
760 if (options.validate) {
761 tint::val::Result res;
762 #ifdef TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
763 res = tint::val::MslUsingMetalAPI(result.msl);
764 #else
765 #ifdef _WIN32
766 const char* default_xcrun_exe = "metal.exe";
767 #else
768 const char* default_xcrun_exe = "xcrun";
769 #endif
770 auto xcrun = tint::utils::Command::LookPath(
771 options.xcrun_path.empty() ? default_xcrun_exe : options.xcrun_path);
772 if (xcrun.Found()) {
773 res = tint::val::Msl(xcrun.Path(), result.msl);
774 } else {
775 res.output = "xcrun executable not found. Cannot validate.";
776 res.failed = true;
777 }
778 #endif // TINT_ENABLE_MSL_VALIDATION_USING_METAL_API
779 if (res.failed) {
780 std::cerr << res.output << std::endl;
781 return false;
782 }
783 }
784
785 return true;
786 #else
787 (void)program;
788 (void)options;
789 std::cerr << "MSL writer not enabled in tint build" << std::endl;
790 return false;
791 #endif // TINT_BUILD_MSL_WRITER
792 }
793
794 /// Generate HLSL code for a program.
795 /// @param program the program to generate
796 /// @param options the options that Tint was invoked with
797 /// @returns true on success
GenerateHlsl(const tint::Program * program,const Options & options)798 bool GenerateHlsl(const tint::Program* program, const Options& options) {
799 #if TINT_BUILD_HLSL_WRITER
800 // TODO(jrprice): Provide a way for the user to set non-default options.
801 tint::writer::hlsl::Options gen_options;
802 gen_options.disable_workgroup_init = options.disable_workgroup_init;
803 auto result = tint::writer::hlsl::Generate(program, gen_options);
804 if (!result.success) {
805 PrintWGSL(std::cerr, *program);
806 std::cerr << "Failed to generate: " << result.error << std::endl;
807 return false;
808 }
809
810 if (!WriteFile(options.output_file, "w", result.hlsl)) {
811 return false;
812 }
813
814 if (options.validate) {
815 tint::val::Result res;
816 if (options.use_fxc) {
817 #ifdef _WIN32
818 res = tint::val::HlslUsingFXC(result.hlsl, result.entry_points);
819 #else
820 res.failed = true;
821 res.output = "FXC can only be used on Windows. Sorry :X";
822 #endif // _WIN32
823 } else {
824 auto dxc = tint::utils::Command::LookPath(
825 options.dxc_path.empty() ? "dxc" : options.dxc_path);
826 if (dxc.Found()) {
827 res = tint::val::HlslUsingDXC(dxc.Path(), result.hlsl,
828 result.entry_points);
829 } else {
830 res.failed = true;
831 res.output = "DXC executable not found. Cannot validate";
832 }
833 }
834 if (res.failed) {
835 std::cerr << res.output << std::endl;
836 return false;
837 }
838 }
839
840 return true;
841 #else
842 (void)program;
843 (void)options;
844 std::cerr << "HLSL writer not enabled in tint build" << std::endl;
845 return false;
846 #endif // TINT_BUILD_HLSL_WRITER
847 }
848
849 #if TINT_BUILD_GLSL_WRITER
pipeline_stage_to_esh_language(tint::ast::PipelineStage stage)850 EShLanguage pipeline_stage_to_esh_language(tint::ast::PipelineStage stage) {
851 switch (stage) {
852 case tint::ast::PipelineStage::kFragment:
853 return EShLangFragment;
854 case tint::ast::PipelineStage::kVertex:
855 return EShLangVertex;
856 case tint::ast::PipelineStage::kCompute:
857 return EShLangCompute;
858 default:
859 TINT_ASSERT(AST, false);
860 return EShLangVertex;
861 }
862 }
863 #endif
864
865 /// Generate GLSL code for a program.
866 /// @param program the program to generate
867 /// @param options the options that Tint was invoked with
868 /// @returns true on success
GenerateGlsl(const tint::Program * program,const Options & options)869 bool GenerateGlsl(const tint::Program* program, const Options& options) {
870 #if TINT_BUILD_GLSL_WRITER
871 bool success = true;
872 if (options.validate) {
873 glslang::InitializeProcess();
874 }
875 tint::writer::glsl::Options gen_options;
876 tint::inspector::Inspector inspector(program);
877 for (auto& entry_point : inspector.GetEntryPoints()) {
878 auto result =
879 tint::writer::glsl::Generate(program, gen_options, entry_point.name);
880 if (!result.success) {
881 PrintWGSL(std::cerr, *program);
882 std::cerr << "Failed to generate: " << result.error << std::endl;
883 return false;
884 }
885
886 if (!WriteFile(options.output_file, "w", result.glsl)) {
887 return false;
888 }
889
890 if (options.validate) {
891 for (auto entry_pt : result.entry_points) {
892 EShLanguage lang = pipeline_stage_to_esh_language(entry_pt.second);
893 glslang::TShader shader(lang);
894 const char* strings[1] = {result.glsl.c_str()};
895 int lengths[1] = {static_cast<int>(result.glsl.length())};
896 shader.setStringsWithLengths(strings, lengths, 1);
897 shader.setEntryPoint("main");
898 bool glslang_result =
899 shader.parse(&glslang::DefaultTBuiltInResource, 310, EEsProfile,
900 false, false, EShMsgDefault);
901 if (!glslang_result) {
902 std::cerr << "Error parsing GLSL shader:\n"
903 << shader.getInfoLog() << "\n"
904 << shader.getInfoDebugLog() << "\n";
905 success = false;
906 }
907 }
908 }
909 }
910 return success;
911 #else
912 (void)program;
913 (void)options;
914 std::cerr << "GLSL writer not enabled in tint build" << std::endl;
915 return false;
916 #endif // TINT_BUILD_GLSL_WRITER
917 }
918
919 } // namespace
920
main(int argc,const char ** argv)921 int main(int argc, const char** argv) {
922 std::vector<std::string> args(argv, argv + argc);
923 Options options;
924
925 tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
926
927 #if TINT_BUILD_WGSL_WRITER
928 tint::Program::printer = [](const tint::Program* program) {
929 auto result = tint::writer::wgsl::Generate(program, {});
930 if (!result.error.empty()) {
931 return "error: " + result.error;
932 }
933 return result.wgsl;
934 };
935 #endif // TINT_BUILD_WGSL_WRITER
936
937 if (!ParseArgs(args, &options)) {
938 std::cerr << "Failed to parse arguments." << std::endl;
939 return 1;
940 }
941
942 if (options.show_help) {
943 std::cout << kUsage << std::endl;
944 return 0;
945 }
946
947 // Implement output format defaults.
948 if (options.format == Format::kNone) {
949 // Try inferring from filename.
950 options.format = infer_format(options.output_file);
951 }
952 if (options.format == Format::kNone) {
953 // Ultimately, default to SPIR-V assembly. That's nice for interactive use.
954 options.format = Format::kSpvAsm;
955 }
956
957 auto diag_printer = tint::diag::Printer::create(stderr, true);
958 tint::diag::Formatter diag_formatter;
959
960 std::unique_ptr<tint::Program> program;
961 std::unique_ptr<tint::Source::File> source_file;
962
963 enum class InputFormat {
964 kUnknown,
965 kWgsl,
966 kSpirvBin,
967 kSpirvAsm,
968 };
969 auto input_format = InputFormat::kUnknown;
970
971 if (options.input_filename.size() > 5 &&
972 options.input_filename.substr(options.input_filename.size() - 5) ==
973 ".wgsl") {
974 input_format = InputFormat::kWgsl;
975 } else if (options.input_filename.size() > 4 &&
976 options.input_filename.substr(options.input_filename.size() - 4) ==
977 ".spv") {
978 input_format = InputFormat::kSpirvBin;
979 } else if (options.input_filename.size() > 7 &&
980 options.input_filename.substr(options.input_filename.size() - 7) ==
981 ".spvasm") {
982 input_format = InputFormat::kSpirvAsm;
983 }
984
985 switch (input_format) {
986 case InputFormat::kUnknown: {
987 std::cerr << "Unknown input format" << std::endl;
988 return 1;
989 }
990 case InputFormat::kWgsl: {
991 #if TINT_BUILD_WGSL_READER
992 std::vector<uint8_t> data;
993 if (!ReadFile<uint8_t>(options.input_filename, &data)) {
994 return 1;
995 }
996 source_file = std::make_unique<tint::Source::File>(
997 options.input_filename, std::string(data.begin(), data.end()));
998 program = std::make_unique<tint::Program>(
999 tint::reader::wgsl::Parse(source_file.get()));
1000 break;
1001 #else
1002 std::cerr << "Tint not built with the WGSL reader enabled" << std::endl;
1003 return 1;
1004 #endif // TINT_BUILD_WGSL_READER
1005 }
1006 case InputFormat::kSpirvBin: {
1007 #if TINT_BUILD_SPV_READER
1008 std::vector<uint32_t> data;
1009 if (!ReadFile<uint32_t>(options.input_filename, &data)) {
1010 return 1;
1011 }
1012 program =
1013 std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
1014 break;
1015 #else
1016 std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
1017 return 1;
1018 #endif // TINT_BUILD_SPV_READER
1019 }
1020 case InputFormat::kSpirvAsm: {
1021 #if TINT_BUILD_SPV_READER
1022 std::vector<char> text;
1023 if (!ReadFile<char>(options.input_filename, &text)) {
1024 return 1;
1025 }
1026 // Use Vulkan 1.1, since this is what Tint, internally, is expecting.
1027 spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1);
1028 tools.SetMessageConsumer([](spv_message_level_t, const char*,
1029 const spv_position_t& pos, const char* msg) {
1030 std::cerr << (pos.line + 1) << ":" << (pos.column + 1) << ": " << msg
1031 << std::endl;
1032 });
1033 std::vector<uint32_t> data;
1034 if (!tools.Assemble(text.data(), text.size(), &data,
1035 SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS)) {
1036 return 1;
1037 }
1038 program =
1039 std::make_unique<tint::Program>(tint::reader::spirv::Parse(data));
1040 break;
1041 #else
1042 std::cerr << "Tint not built with the SPIR-V reader enabled" << std::endl;
1043 return 1;
1044 #endif // TINT_BUILD_SPV_READER
1045 }
1046 }
1047
1048 if (!program) {
1049 std::cerr << "Failed to parse input file: " << options.input_filename
1050 << std::endl;
1051 return 1;
1052 }
1053 if (program->Diagnostics().count() > 0) {
1054 if (!program->IsValid() && input_format != InputFormat::kWgsl) {
1055 // Invalid program from a non-wgsl source. Print the WGSL, to help
1056 // understand the diagnostics.
1057 PrintWGSL(std::cout, *program);
1058 }
1059 diag_formatter.format(program->Diagnostics(), diag_printer.get());
1060 }
1061
1062 if (!program->IsValid()) {
1063 return 1;
1064 }
1065 if (options.parse_only) {
1066 return 1;
1067 }
1068
1069 tint::transform::Manager transform_manager;
1070 tint::transform::DataMap transform_inputs;
1071 for (const auto& name : options.transforms) {
1072 // TODO(dsinclair): The vertex pulling transform requires setup code to
1073 // be run that needs user input. Should we find a way to support that here
1074 // maybe through a provided file?
1075
1076 if (name == "first_index_offset") {
1077 transform_inputs.Add<tint::transform::FirstIndexOffset::BindingPoint>(0,
1078 0);
1079 transform_manager.Add<tint::transform::FirstIndexOffset>();
1080 } else if (name == "fold_trivial_single_use_lets") {
1081 transform_manager.Add<tint::transform::FoldTrivialSingleUseLets>();
1082 } else if (name == "renamer") {
1083 transform_manager.Add<tint::transform::Renamer>();
1084 } else if (name == "robustness") {
1085 transform_manager.Add<tint::transform::Robustness>();
1086 } else {
1087 std::cerr << "Unknown transform name: " << name << std::endl;
1088 return 1;
1089 }
1090 }
1091
1092 if (options.emit_single_entry_point) {
1093 transform_manager.append(
1094 std::make_unique<tint::transform::SingleEntryPoint>());
1095 transform_inputs.Add<tint::transform::SingleEntryPoint::Config>(
1096 options.ep_name);
1097 }
1098
1099 switch (options.format) {
1100 case Format::kMsl: {
1101 #if TINT_BUILD_MSL_WRITER
1102 transform_inputs.Add<tint::transform::Renamer::Config>(
1103 tint::transform::Renamer::Target::kMslKeywords);
1104 transform_manager.Add<tint::transform::Renamer>();
1105 #endif // TINT_BUILD_MSL_WRITER
1106 break;
1107 }
1108 #if TINT_BUILD_GLSL_WRITER
1109 case Format::kGlsl: {
1110 transform_inputs.Add<tint::transform::Renamer::Config>(
1111 tint::transform::Renamer::Target::kGlslKeywords);
1112 transform_manager.Add<tint::transform::Renamer>();
1113 break;
1114 }
1115 #endif // TINT_BUILD_GLSL_WRITER
1116 case Format::kHlsl: {
1117 #if TINT_BUILD_HLSL_WRITER
1118 transform_inputs.Add<tint::transform::Renamer::Config>(
1119 tint::transform::Renamer::Target::kHlslKeywords);
1120 transform_manager.Add<tint::transform::Renamer>();
1121 #endif // TINT_BUILD_HLSL_WRITER
1122 break;
1123 }
1124 default:
1125 break;
1126 }
1127
1128 auto out = transform_manager.Run(program.get(), std::move(transform_inputs));
1129 if (!out.program.IsValid()) {
1130 PrintWGSL(std::cerr, out.program);
1131 diag_formatter.format(out.program.Diagnostics(), diag_printer.get());
1132 return 1;
1133 }
1134
1135 *program = std::move(out.program);
1136
1137 if (options.dump_inspector_bindings) {
1138 std::cout << std::string(80, '-') << std::endl;
1139 tint::inspector::Inspector inspector(program.get());
1140 auto entry_points = inspector.GetEntryPoints();
1141 if (!inspector.error().empty()) {
1142 std::cerr << "Failed to get entry points from Inspector: "
1143 << inspector.error() << std::endl;
1144 return 1;
1145 }
1146
1147 for (auto& entry_point : entry_points) {
1148 auto bindings = inspector.GetResourceBindings(entry_point.name);
1149 if (!inspector.error().empty()) {
1150 std::cerr << "Failed to get bindings from Inspector: "
1151 << inspector.error() << std::endl;
1152 return 1;
1153 }
1154 std::cout << "Entry Point = " << entry_point.name << std::endl;
1155 for (auto& binding : bindings) {
1156 std::cout << "\t[" << binding.bind_group << "][" << binding.binding
1157 << "]:" << std::endl;
1158 std::cout << "\t\t resource_type = "
1159 << ResourceTypeToString(binding.resource_type) << std::endl;
1160 std::cout << "\t\t dim = " << TextureDimensionToString(binding.dim)
1161 << std::endl;
1162 std::cout << "\t\t sampled_kind = "
1163 << SampledKindToString(binding.sampled_kind) << std::endl;
1164 std::cout << "\t\t image_format = "
1165 << ImageFormatToString(binding.image_format) << std::endl;
1166 }
1167 }
1168 std::cout << std::string(80, '-') << std::endl;
1169 }
1170
1171 bool success = false;
1172 switch (options.format) {
1173 case Format::kSpirv:
1174 case Format::kSpvAsm:
1175 success = GenerateSpirv(program.get(), options);
1176 break;
1177 case Format::kWgsl:
1178 success = GenerateWgsl(program.get(), options);
1179 break;
1180 case Format::kMsl:
1181 success = GenerateMsl(program.get(), options);
1182 break;
1183 case Format::kHlsl:
1184 success = GenerateHlsl(program.get(), options);
1185 break;
1186 case Format::kGlsl:
1187 success = GenerateGlsl(program.get(), options);
1188 break;
1189 default:
1190 std::cerr << "Unknown output format specified" << std::endl;
1191 return 1;
1192 }
1193 if (!success) {
1194 return 1;
1195 }
1196
1197 return 0;
1198 }
1199