• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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