1 // Copyright (c) 2016 Google Inc.
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 <algorithm>
16 #include <cassert>
17 #include <cstring>
18 #include <fstream>
19 #include <iostream>
20 #include <memory>
21 #include <sstream>
22 #include <string>
23 #include <vector>
24
25 #include "source/opt/log.h"
26 #include "source/spirv_target_env.h"
27 #include "source/util/string_utils.h"
28 #include "spirv-tools/libspirv.hpp"
29 #include "spirv-tools/optimizer.hpp"
30 #include "tools/io.h"
31 #include "tools/util/cli_consumer.h"
32
33 namespace {
34
35 // Status and actions to perform after parsing command-line arguments.
36 enum OptActions { OPT_CONTINUE, OPT_STOP };
37
38 struct OptStatus {
39 OptActions action;
40 int code;
41 };
42
43 // Message consumer for this tool. Used to emit diagnostics during
44 // initialization and setup. Note that |source| and |position| are irrelevant
45 // here because we are still not processing a SPIR-V input file.
opt_diagnostic(spv_message_level_t level,const char *,const spv_position_t &,const char * message)46 void opt_diagnostic(spv_message_level_t level, const char* /*source*/,
47 const spv_position_t& /*positon*/, const char* message) {
48 if (level == SPV_MSG_ERROR) {
49 fprintf(stderr, "error: ");
50 }
51 fprintf(stderr, "%s\n", message);
52 }
53
GetListOfPassesAsString(const spvtools::Optimizer & optimizer)54 std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) {
55 std::stringstream ss;
56 for (const auto& name : optimizer.GetPassNames()) {
57 ss << "\n\t\t" << name;
58 }
59 return ss.str();
60 }
61
62 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
63
GetLegalizationPasses()64 std::string GetLegalizationPasses() {
65 spvtools::Optimizer optimizer(kDefaultEnvironment);
66 optimizer.RegisterLegalizationPasses();
67 return GetListOfPassesAsString(optimizer);
68 }
69
GetOptimizationPasses()70 std::string GetOptimizationPasses() {
71 spvtools::Optimizer optimizer(kDefaultEnvironment);
72 optimizer.RegisterPerformancePasses();
73 return GetListOfPassesAsString(optimizer);
74 }
75
GetSizePasses()76 std::string GetSizePasses() {
77 spvtools::Optimizer optimizer(kDefaultEnvironment);
78 optimizer.RegisterSizePasses();
79 return GetListOfPassesAsString(optimizer);
80 }
81
GetWebGPUPasses()82 std::string GetWebGPUPasses() {
83 spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0);
84 optimizer.RegisterWebGPUPasses();
85 return GetListOfPassesAsString(optimizer);
86 }
87
PrintUsage(const char * program)88 void PrintUsage(const char* program) {
89 // NOTE: Please maintain flags in lexicographical order.
90 printf(
91 R"(%s - Optimize a SPIR-V binary file.
92
93 USAGE: %s [options] [<input>] -o <output>
94
95 The SPIR-V binary is read from <input>. If no file is specified,
96 or if <input> is "-", then the binary is read from standard input.
97 if <output> is "-", then the optimized output is written to
98 standard output.
99
100 NOTE: The optimizer is a work in progress.
101
102 Options (in lexicographical order):
103 --ccp
104 Apply the conditional constant propagation transform. This will
105 propagate constant values throughout the program, and simplify
106 expressions and conditional jumps with known predicate
107 values. Performed on entry point call tree functions and
108 exported functions.
109 --cfg-cleanup
110 Cleanup the control flow graph. This will remove any unnecessary
111 code from the CFG like unreachable code. Performed on entry
112 point call tree functions and exported functions.
113 --combine-access-chains
114 Combines chained access chains to produce a single instruction
115 where possible.
116 --compact-ids
117 Remap result ids to a compact range starting from %%1 and without
118 any gaps.
119 --convert-local-access-chains
120 Convert constant index access chain loads/stores into
121 equivalent load/stores with inserts and extracts. Performed
122 on function scope variables referenced only with load, store,
123 and constant index access chains in entry point call tree
124 functions.
125 --copy-propagate-arrays
126 Does propagation of memory references when an array is a copy of
127 another. It will only propagate an array if the source is never
128 written to, and the only store to the target is the copy.
129 --eliminate-common-uniform
130 Perform load/load elimination for duplicate uniform values.
131 Converts any constant index access chain uniform loads into
132 its equivalent load and extract. Some loads will be moved
133 to facilitate sharing. Performed only on entry point
134 call tree functions.
135 --eliminate-dead-branches
136 Convert conditional branches with constant condition to the
137 indicated unconditional brranch. Delete all resulting dead
138 code. Performed only on entry point call tree functions.
139 --eliminate-dead-code-aggressive
140 Delete instructions which do not contribute to a function's
141 output. Performed only on entry point call tree functions.
142 --eliminate-dead-const
143 Eliminate dead constants.
144 --eliminate-dead-functions
145 Deletes functions that cannot be reached from entry points or
146 exported functions.
147 --eliminate-dead-inserts
148 Deletes unreferenced inserts into composites, most notably
149 unused stores to vector components, that are not removed by
150 aggressive dead code elimination.
151 --eliminate-dead-variables
152 Deletes module scope variables that are not referenced.
153 --eliminate-insert-extract
154 DEPRECATED. This pass has been replaced by the simplification
155 pass, and that pass will be run instead.
156 See --simplify-instructions.
157 --eliminate-local-multi-store
158 Replace stores and loads of function scope variables that are
159 stored multiple times. Performed on variables referenceed only
160 with loads and stores. Performed only on entry point call tree
161 functions.
162 --eliminate-local-single-block
163 Perform single-block store/load and load/load elimination.
164 Performed only on function scope variables in entry point
165 call tree functions.
166 --eliminate-local-single-store
167 Replace stores and loads of function scope variables that are
168 only stored once. Performed on variables referenceed only with
169 loads and stores. Performed only on entry point call tree
170 functions.
171 --flatten-decorations
172 Replace decoration groups with repeated OpDecorate and
173 OpMemberDecorate instructions.
174 --fold-spec-const-op-composite
175 Fold the spec constants defined by OpSpecConstantOp or
176 OpSpecConstantComposite instructions to front-end constants
177 when possible.
178 --freeze-spec-const
179 Freeze the values of specialization constants to their default
180 values.
181 --if-conversion
182 Convert if-then-else like assignments into OpSelect.
183 --inline-entry-points-exhaustive
184 Exhaustively inline all function calls in entry point call tree
185 functions. Currently does not inline calls to functions with
186 early return in a loop.
187 --legalize-hlsl
188 Runs a series of optimizations that attempts to take SPIR-V
189 generated by an HLSL front-end and generates legal Vulkan SPIR-V.
190 The optimizations are:
191 %s
192
193 Note this does not guarantee legal code. This option passes the
194 option --relax-logical-pointer to the validator.
195 --local-redundancy-elimination
196 Looks for instructions in the same basic block that compute the
197 same value, and deletes the redundant ones.
198 --loop-fission
199 Splits any top level loops in which the register pressure has
200 exceeded a given threshold. The threshold must follow the use of
201 this flag and must be a positive integer value.
202 --loop-fusion
203 Identifies adjacent loops with the same lower and upper bound.
204 If this is legal, then merge the loops into a single loop.
205 Includes heuristics to ensure it does not increase number of
206 registers too much, while reducing the number of loads from
207 memory. Takes an additional positive integer argument to set
208 the maximum number of registers.
209 --loop-invariant-code-motion
210 Identifies code in loops that has the same value for every
211 iteration of the loop, and move it to the loop pre-header.
212 --loop-unroll
213 Fully unrolls loops marked with the Unroll flag
214 --loop-unroll-partial
215 Partially unrolls loops marked with the Unroll flag. Takes an
216 additional non-0 integer argument to set the unroll factor, or
217 how many times a loop body should be duplicated
218 --loop-peeling
219 Execute few first (respectively last) iterations before
220 (respectively after) the loop if it can elide some branches.
221 --loop-peeling-threshold
222 Takes a non-0 integer argument to set the loop peeling code size
223 growth threshold. The threshold prevents the loop peeling
224 from happening if the code size increase created by
225 the optimization is above the threshold.
226 --max-id-bound=<n>
227 Sets the maximum value for the id bound for the moudle. The
228 default is the minimum value for this limit, 0x3FFFFF. See
229 section 2.17 of the Spir-V specification.
230 --merge-blocks
231 Join two blocks into a single block if the second has the
232 first as its only predecessor. Performed only on entry point
233 call tree functions.
234 --merge-return
235 Changes functions that have multiple return statements so they
236 have a single return statement.
237
238 For structured control flow it is assumed that the only
239 unreachable blocks in the function are trivial merge and continue
240 blocks.
241
242 A trivial merge block contains the label and an OpUnreachable
243 instructions, nothing else. A trivial continue block contain a
244 label and an OpBranch to the header, nothing else.
245
246 These conditions are guaranteed to be met after running
247 dead-branch elimination.
248 --loop-unswitch
249 Hoists loop-invariant conditionals out of loops by duplicating
250 the loop on each branch of the conditional and adjusting each
251 copy of the loop.
252 -O
253 Optimize for performance. Apply a sequence of transformations
254 in an attempt to improve the performance of the generated
255 code. For this version of the optimizer, this flag is equivalent
256 to specifying the following optimization code names:
257 %s
258 -Os
259 Optimize for size. Apply a sequence of transformations in an
260 attempt to minimize the size of the generated code. For this
261 version of the optimizer, this flag is equivalent to specifying
262 the following optimization code names:
263 %s
264
265 NOTE: The specific transformations done by -O and -Os change
266 from release to release.
267 -Oconfig=<file>
268 Apply the sequence of transformations indicated in <file>.
269 This file contains a sequence of strings separated by whitespace
270 (tabs, newlines or blanks). Each string is one of the flags
271 accepted by spirv-opt. Optimizations will be applied in the
272 sequence they appear in the file. This is equivalent to
273 specifying all the flags on the command line. For example,
274 given the file opts.cfg with the content:
275
276 --inline-entry-points-exhaustive
277 --eliminate-dead-code-aggressive
278
279 The following two invocations to spirv-opt are equivalent:
280
281 $ spirv-opt -Oconfig=opts.cfg program.spv
282
283 $ spirv-opt --inline-entry-points-exhaustive \
284 --eliminate-dead-code-aggressive program.spv
285
286 Lines starting with the character '#' in the configuration
287 file indicate a comment and will be ignored.
288
289 The -O, -Os, and -Oconfig flags act as macros. Using one of them
290 is equivalent to explicitly inserting the underlying flags at
291 that position in the command line. For example, the invocation
292 'spirv-opt --merge-blocks -O ...' applies the transformation
293 --merge-blocks followed by all the transformations implied by
294 -O.
295 --print-all
296 Print SPIR-V assembly to standard error output before each pass
297 and after the last pass.
298 --private-to-local
299 Change the scope of private variables that are used in a single
300 function to that function.
301 --reduce-load-size
302 Replaces loads of composite objects where not every component is
303 used by loads of just the elements that are used.
304 --redundancy-elimination
305 Looks for instructions in the same function that compute the
306 same value, and deletes the redundant ones.
307 --relax-struct-store
308 Allow store from one struct type to a different type with
309 compatible layout and members. This option is forwarded to the
310 validator.
311 --remove-duplicates
312 Removes duplicate types, decorations, capabilities and extension
313 instructions.
314 --replace-invalid-opcode
315 Replaces instructions whose opcode is valid for shader modules,
316 but not for the current shader stage. To have an effect, all
317 entry points must have the same execution model.
318 --ssa-rewrite
319 Replace loads and stores to function local variables with
320 operations on SSA IDs.
321 --scalar-replacement[=<n>]
322 Replace aggregate function scope variables that are only accessed
323 via their elements with new function variables representing each
324 element. <n> is a limit on the size of the aggragates that will
325 be replaced. 0 means there is no limit. The default value is
326 100.
327 --set-spec-const-default-value "<spec id>:<default value> ..."
328 Set the default values of the specialization constants with
329 <spec id>:<default value> pairs specified in a double-quoted
330 string. <spec id>:<default value> pairs must be separated by
331 blank spaces, and in each pair, spec id and default value must
332 be separated with colon ':' without any blank spaces in between.
333 e.g.: --set-spec-const-default-value "1:100 2:400"
334 --simplify-instructions
335 Will simplify all instructions in the function as much as
336 possible.
337 --skip-validation
338 Will not validate the SPIR-V before optimizing. If the SPIR-V
339 is invalid, the optimizer may fail or generate incorrect code.
340 This options should be used rarely, and with caution.
341 --strength-reduction
342 Replaces instructions with equivalent and less expensive ones.
343 --strip-debug
344 Remove all debug instructions.
345 --strip-reflect
346 Remove all reflection information. For now, this covers
347 reflection information defined by SPV_GOOGLE_hlsl_functionality1.
348 --target-env=<env>
349 Set the target environment. Without this flag the target
350 enviroment defaults to spv1.3.
351 <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
352 spv1.1, spv1.2, spv1.3, or webgpu0.
353 --time-report
354 Print the resource utilization of each pass (e.g., CPU time,
355 RSS) to standard error output. Currently it supports only Unix
356 systems. This option is the same as -ftime-report in GCC. It
357 prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
358 USR/SYS time are returned by getrusage() and can have a small
359 error.
360 --upgrade-memory-model
361 Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
362 Transforms memory, image, atomic and barrier operations to conform
363 to that model's requirements.
364 --vector-dce
365 This pass looks for components of vectors that are unused, and
366 removes them from the vector. Note this would still leave around
367 lots of dead code that a pass of ADCE will be able to remove.
368 --webgpu-mode
369 Turns on the prescribed passes for WebGPU and sets the target
370 environmet to webgpu0. Other passes may be turned on via
371 additional flags, but such combinations are not tested.
372 Using --target-env with this flag is not allowed.
373
374 This flag is the equivalent of passing in --target-env=webgpu0
375 and specifying the following optimization code names:
376 %s
377
378 NOTE: This flag is a WIP and its behaviour is subject to change.
379 --workaround-1209
380 Rewrites instructions for which there are known driver bugs to
381 avoid triggering those bugs.
382 Current workarounds: Avoid OpUnreachable in loops.
383 --unify-const
384 Remove the duplicated constants.
385 -h, --help
386 Print this help.
387 --version
388 Display optimizer version information.
389 )",
390 program, program, GetLegalizationPasses().c_str(),
391 GetOptimizationPasses().c_str(), GetSizePasses().c_str(),
392 GetWebGPUPasses().c_str());
393 }
394
395 // Reads command-line flags the file specified in |oconfig_flag|. This string
396 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
397 // string and extracts the file name after the '=' sign.
398 //
399 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
400 //
401 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)402 bool ReadFlagsFromFile(const char* oconfig_flag,
403 std::vector<std::string>* file_flags) {
404 const char* fname = strchr(oconfig_flag, '=');
405 if (fname == nullptr || fname[0] != '=') {
406 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
407 oconfig_flag);
408 return false;
409 }
410 fname++;
411
412 std::ifstream input_file;
413 input_file.open(fname);
414 if (input_file.fail()) {
415 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
416 fname);
417 return false;
418 }
419
420 std::string line;
421 while (std::getline(input_file, line)) {
422 // Ignore empty lines and lines starting with the comment marker '#'.
423 if (line.length() == 0 || line[0] == '#') {
424 continue;
425 }
426
427 // Tokenize the line. Add all found tokens to the list of found flags. This
428 // mimics the way the shell will parse whitespace on the command line. NOTE:
429 // This does not support quoting and it is not intended to.
430 std::istringstream iss(line);
431 while (!iss.eof()) {
432 std::string flag;
433 iss >> flag;
434 file_flags->push_back(flag);
435 }
436 }
437
438 return true;
439 }
440
441 OptStatus ParseFlags(int argc, const char** argv,
442 spvtools::Optimizer* optimizer, const char** in_file,
443 const char** out_file,
444 spvtools::ValidatorOptions* validator_options,
445 spvtools::OptimizerOptions* optimizer_options);
446
447 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
448 // the spirv-opt binary (used to build a new argv vector for the recursive
449 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
450 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
451 // |optimizer_options| are as in ParseFlags.
452 //
453 // This returns the same OptStatus instance returned by ParseFlags.
ParseOconfigFlag(const char * prog_name,const char * opt_flag,spvtools::Optimizer * optimizer,const char ** in_file,const char ** out_file,spvtools::ValidatorOptions * validator_options,spvtools::OptimizerOptions * optimizer_options)454 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
455 spvtools::Optimizer* optimizer, const char** in_file,
456 const char** out_file,
457 spvtools::ValidatorOptions* validator_options,
458 spvtools::OptimizerOptions* optimizer_options) {
459 std::vector<std::string> flags;
460 flags.push_back(prog_name);
461
462 std::vector<std::string> file_flags;
463 if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
464 spvtools::Error(opt_diagnostic, nullptr, {},
465 "Could not read optimizer flags from configuration file");
466 return {OPT_STOP, 1};
467 }
468 flags.insert(flags.end(), file_flags.begin(), file_flags.end());
469
470 const char** new_argv = new const char*[flags.size()];
471 for (size_t i = 0; i < flags.size(); i++) {
472 if (flags[i].find("-Oconfig=") != std::string::npos) {
473 spvtools::Error(
474 opt_diagnostic, nullptr, {},
475 "Flag -Oconfig= may not be used inside the configuration file");
476 return {OPT_STOP, 1};
477 }
478 new_argv[i] = flags[i].c_str();
479 }
480
481 auto ret_val =
482 ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
483 out_file, validator_options, optimizer_options);
484 delete[] new_argv;
485 return ret_val;
486 }
487
488 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
489 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
490 // form '--pass_name=arg'. Since spirv-opt also accepts the other form, this
491 // function makes the necessary conversion.
492 //
493 // Pass flags that require additional arguments should be handled here. Note
494 // that additional arguments should be given as a single string. If the flag
495 // requires more than one argument, the pass creator in
496 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
497 // handler for --set-spec-const-default-value).
498 //
499 // If the argument requests one of the passes that need an additional argument,
500 // |argi| is modified to point past the current argument, and the string
501 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
502 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)503 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
504 const char* cur_arg = argv[*argi];
505 const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
506 std::ostringstream canonical_arg;
507 canonical_arg << cur_arg;
508
509 // NOTE: DO NOT ADD NEW FLAGS HERE.
510 //
511 // These flags are supported for backwards compatibility. When adding new
512 // passes that need extra arguments in its command-line flag, please make them
513 // use the syntax "--pass_name[=pass_arg].
514 if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
515 0 == strcmp(cur_arg, "--loop-fission") ||
516 0 == strcmp(cur_arg, "--loop-fusion") ||
517 0 == strcmp(cur_arg, "--loop-unroll-partial") ||
518 0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
519 if (next_arg) {
520 canonical_arg << "=" << next_arg;
521 ++(*argi);
522 }
523 }
524
525 return canonical_arg.str();
526 }
527
528 // Parses command-line flags. |argc| contains the number of command-line flags.
529 // |argv| points to an array of strings holding the flags. |optimizer| is the
530 // Optimizer instance used to optimize the program.
531 //
532 // On return, this function stores the name of the input program in |in_file|.
533 // The name of the output file in |out_file|. The return value indicates whether
534 // optimization should continue and a status code indicating an error or
535 // success.
ParseFlags(int argc,const char ** argv,spvtools::Optimizer * optimizer,const char ** in_file,const char ** out_file,spvtools::ValidatorOptions * validator_options,spvtools::OptimizerOptions * optimizer_options)536 OptStatus ParseFlags(int argc, const char** argv,
537 spvtools::Optimizer* optimizer, const char** in_file,
538 const char** out_file,
539 spvtools::ValidatorOptions* validator_options,
540 spvtools::OptimizerOptions* optimizer_options) {
541 std::vector<std::string> pass_flags;
542 bool target_env_set = false;
543 bool webgpu_mode_set = false;
544 for (int argi = 1; argi < argc; ++argi) {
545 const char* cur_arg = argv[argi];
546 if ('-' == cur_arg[0]) {
547 if (0 == strcmp(cur_arg, "--version")) {
548 spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
549 spvSoftwareVersionDetailsString());
550 return {OPT_STOP, 0};
551 } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
552 PrintUsage(argv[0]);
553 return {OPT_STOP, 0};
554 } else if (0 == strcmp(cur_arg, "-o")) {
555 if (!*out_file && argi + 1 < argc) {
556 *out_file = argv[++argi];
557 } else {
558 PrintUsage(argv[0]);
559 return {OPT_STOP, 1};
560 }
561 } else if ('\0' == cur_arg[1]) {
562 // Setting a filename of "-" to indicate stdin.
563 if (!*in_file) {
564 *in_file = cur_arg;
565 } else {
566 spvtools::Error(opt_diagnostic, nullptr, {},
567 "More than one input file specified");
568 return {OPT_STOP, 1};
569 }
570 } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
571 OptStatus status =
572 ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
573 validator_options, optimizer_options);
574 if (status.action != OPT_CONTINUE) {
575 return status;
576 }
577 } else if (0 == strcmp(cur_arg, "--skip-validation")) {
578 optimizer_options->set_run_validator(false);
579 } else if (0 == strcmp(cur_arg, "--print-all")) {
580 optimizer->SetPrintAll(&std::cerr);
581 } else if (0 == strcmp(cur_arg, "--time-report")) {
582 optimizer->SetTimeReport(&std::cerr);
583 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
584 validator_options->SetRelaxStructStore(true);
585 } else if (0 == strncmp(cur_arg, "--max-id-bound=",
586 sizeof("--max-id-bound=") - 1)) {
587 auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
588 // Will not allow values in the range [2^31,2^32).
589 uint32_t max_id_bound =
590 static_cast<uint32_t>(atoi(split_flag.second.c_str()));
591
592 // That SPIR-V mandates the minimum value for max id bound but
593 // implementations may allow higher minimum bounds.
594 if (max_id_bound < kDefaultMaxIdBound) {
595 spvtools::Error(opt_diagnostic, nullptr, {},
596 "The max id bound must be at least 0x3FFFFF");
597 return {OPT_STOP, 1};
598 }
599 optimizer_options->set_max_id_bound(max_id_bound);
600 validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
601 max_id_bound);
602 } else if (0 == strncmp(cur_arg,
603 "--target-env=", sizeof("--target-env=") - 1)) {
604 if (webgpu_mode_set) {
605 spvtools::Error(opt_diagnostic, nullptr, {},
606 "Cannot use both --webgpu-mode and --target-env at "
607 "the same time");
608 return {OPT_STOP, 1};
609 }
610 const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
611 const auto target_env_str = split_flag.second.c_str();
612 spv_target_env target_env;
613 if (!spvParseTargetEnv(target_env_str, &target_env)) {
614 spvtools::Error(opt_diagnostic, nullptr, {},
615 "Invalid value passed to --target-env");
616 return {OPT_STOP, 1};
617 }
618 optimizer->SetTargetEnv(target_env);
619 } else if (0 == strcmp(cur_arg, "--webgpu-mode")) {
620 if (target_env_set) {
621 spvtools::Error(opt_diagnostic, nullptr, {},
622 "Cannot use both --webgpu-mode and --target-env at "
623 "the same time");
624 return {OPT_STOP, 1};
625 }
626
627 optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0);
628 optimizer->RegisterWebGPUPasses();
629 } else {
630 // Some passes used to accept the form '--pass arg', canonicalize them
631 // to '--pass=arg'.
632 pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
633
634 // If we were requested to legalize SPIR-V generated from the HLSL
635 // front-end, skip validation.
636 if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
637 validator_options->SetRelaxLogicalPointer(true);
638 }
639 }
640 } else {
641 if (!*in_file) {
642 *in_file = cur_arg;
643 } else {
644 spvtools::Error(opt_diagnostic, nullptr, {},
645 "More than one input file specified");
646 return {OPT_STOP, 1};
647 }
648 }
649 }
650
651 if (!optimizer->RegisterPassesFromFlags(pass_flags)) {
652 return {OPT_STOP, 1};
653 }
654
655 return {OPT_CONTINUE, 0};
656 }
657
658 } // namespace
659
main(int argc,const char ** argv)660 int main(int argc, const char** argv) {
661 const char* in_file = nullptr;
662 const char* out_file = nullptr;
663
664 spv_target_env target_env = kDefaultEnvironment;
665
666 spvtools::Optimizer optimizer(target_env);
667 optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
668
669 spvtools::ValidatorOptions validator_options;
670 spvtools::OptimizerOptions optimizer_options;
671 OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
672 &validator_options, &optimizer_options);
673 optimizer_options.set_validator_options(validator_options);
674
675 if (status.action == OPT_STOP) {
676 return status.code;
677 }
678
679 if (out_file == nullptr) {
680 spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
681 return 1;
682 }
683
684 std::vector<uint32_t> binary;
685 if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
686 return 1;
687 }
688
689 // By using the same vector as input and output, we save time in the case
690 // that there was no change.
691 bool ok =
692 optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
693
694 if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
695 return 1;
696 }
697
698 return ok ? 0 : 1;
699 }
700