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_5;
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
PrintUsage(const char * program)82 void PrintUsage(const char* program) {
83 std::string target_env_list = spvTargetEnvList(16, 80);
84 // NOTE: Please maintain flags in lexicographical order.
85 printf(
86 R"(%s - Optimize a SPIR-V binary file.
87
88 USAGE: %s [options] [<input>] -o <output>
89
90 The SPIR-V binary is read from <input>. If no file is specified,
91 or if <input> is "-", then the binary is read from standard input.
92 if <output> is "-", then the optimized output is written to
93 standard output.
94
95 NOTE: The optimizer is a work in progress.
96
97 Options (in lexicographical order):)",
98 program, program);
99 printf(R"(
100 --amd-ext-to-khr
101 Replaces the extensions VK_AMD_shader_ballot, VK_AMD_gcn_shader,
102 and VK_AMD_shader_trinary_minmax with equivalent code using core
103 instructions and capabilities.)");
104 printf(R"(
105 --before-hlsl-legalization
106 Forwards this option to the validator. See the validator help
107 for details.)");
108 printf(R"(
109 --ccp
110 Apply the conditional constant propagation transform. This will
111 propagate constant values throughout the program, and simplify
112 expressions and conditional jumps with known predicate
113 values. Performed on entry point call tree functions and
114 exported functions.)");
115 printf(R"(
116 --cfg-cleanup
117 Cleanup the control flow graph. This will remove any unnecessary
118 code from the CFG like unreachable code. Performed on entry
119 point call tree functions and exported functions.)");
120 printf(R"(
121 --combine-access-chains
122 Combines chained access chains to produce a single instruction
123 where possible.)");
124 printf(R"(
125 --compact-ids
126 Remap result ids to a compact range starting from %%1 and without
127 any gaps.)");
128 printf(R"(
129 --convert-local-access-chains
130 Convert constant index access chain loads/stores into
131 equivalent load/stores with inserts and extracts. Performed
132 on function scope variables referenced only with load, store,
133 and constant index access chains in entry point call tree
134 functions.)");
135 printf(R"(
136 --convert-relaxed-to-half
137 Convert all RelaxedPrecision arithmetic operations to half
138 precision, inserting conversion operations where needed.
139 Run after function scope variable load and store elimination
140 for better results. Simplify-instructions, redundancy-elimination
141 and DCE should be run after this pass to eliminate excess
142 conversions. This conversion is useful when the target platform
143 does not support RelaxedPrecision or ignores it. This pass also
144 removes all RelaxedPrecision decorations.)");
145 printf(R"(
146 --copy-propagate-arrays
147 Does propagation of memory references when an array is a copy of
148 another. It will only propagate an array if the source is never
149 written to, and the only store to the target is the copy.)");
150 printf(R"(
151 --decompose-initialized-variables
152 Decomposes initialized variable declarations into a declaration
153 followed by a store of the initial value. This is done to work
154 around known issues with some Vulkan drivers for initialize
155 variables.)");
156 printf(R"(
157 --descriptor-scalar-replacement
158 Replaces every array variable |desc| that has a DescriptorSet
159 and Binding decorations with a new variable for each element of
160 the array. Suppose |desc| was bound at binding |b|. Then the
161 variable corresponding to |desc[i]| will have binding |b+i|.
162 The descriptor set will be the same. All accesses to |desc|
163 must be in OpAccessChain instructions with a literal index for
164 the first index.)");
165 printf(R"(
166 --eliminate-dead-branches
167 Convert conditional branches with constant condition to the
168 indicated unconditional branch. Delete all resulting dead
169 code. Performed only on entry point call tree functions.)");
170 printf(R"(
171 --eliminate-dead-code-aggressive
172 Delete instructions which do not contribute to a function's
173 output. Performed only on entry point call tree functions.)");
174 printf(R"(
175 --eliminate-dead-const
176 Eliminate dead constants.)");
177 printf(R"(
178 --eliminate-dead-functions
179 Deletes functions that cannot be reached from entry points or
180 exported functions.)");
181 printf(R"(
182 --eliminate-dead-inserts
183 Deletes unreferenced inserts into composites, most notably
184 unused stores to vector components, that are not removed by
185 aggressive dead code elimination.)");
186 printf(R"(
187 --eliminate-dead-variables
188 Deletes module scope variables that are not referenced.)");
189 printf(R"(
190 --eliminate-insert-extract
191 DEPRECATED. This pass has been replaced by the simplification
192 pass, and that pass will be run instead.
193 See --simplify-instructions.)");
194 printf(R"(
195 --eliminate-local-multi-store
196 Replace stores and loads of function scope variables that are
197 stored multiple times. Performed on variables referenceed only
198 with loads and stores. Performed only on entry point call tree
199 functions.)");
200 printf(R"(
201 --eliminate-local-single-block
202 Perform single-block store/load and load/load elimination.
203 Performed only on function scope variables in entry point
204 call tree functions.)");
205 printf(R"(
206 --eliminate-local-single-store
207 Replace stores and loads of function scope variables that are
208 only stored once. Performed on variables referenceed only with
209 loads and stores. Performed only on entry point call tree
210 functions.)");
211 printf(R"(
212 --flatten-decorations
213 Replace decoration groups with repeated OpDecorate and
214 OpMemberDecorate instructions.)");
215 printf(R"(
216 --fold-spec-const-op-composite
217 Fold the spec constants defined by OpSpecConstantOp or
218 OpSpecConstantComposite instructions to front-end constants
219 when possible.)");
220 printf(R"(
221 --freeze-spec-const
222 Freeze the values of specialization constants to their default
223 values.)");
224 printf(R"(
225 --graphics-robust-access
226 Clamp indices used to access buffers and internal composite
227 values, providing guarantees that satisfy Vulkan's
228 robustBufferAccess rules.)");
229 printf(R"(
230 --if-conversion
231 Convert if-then-else like assignments into OpSelect.)");
232 printf(R"(
233 --inline-entry-points-exhaustive
234 Exhaustively inline all function calls in entry point call tree
235 functions. Currently does not inline calls to functions with
236 early return in a loop.)");
237 printf(R"(
238 --legalize-hlsl
239 Runs a series of optimizations that attempts to take SPIR-V
240 generated by an HLSL front-end and generates legal Vulkan SPIR-V.
241 The optimizations are:
242 %s
243
244 Note this does not guarantee legal code. This option passes the
245 option --relax-logical-pointer to the validator.)",
246 GetLegalizationPasses().c_str());
247 printf(R"(
248 --local-redundancy-elimination
249 Looks for instructions in the same basic block that compute the
250 same value, and deletes the redundant ones.)");
251 printf(R"(
252 --loop-fission
253 Splits any top level loops in which the register pressure has
254 exceeded a given threshold. The threshold must follow the use of
255 this flag and must be a positive integer value.)");
256 printf(R"(
257 --loop-fusion
258 Identifies adjacent loops with the same lower and upper bound.
259 If this is legal, then merge the loops into a single loop.
260 Includes heuristics to ensure it does not increase number of
261 registers too much, while reducing the number of loads from
262 memory. Takes an additional positive integer argument to set
263 the maximum number of registers.)");
264 printf(R"(
265 --loop-invariant-code-motion
266 Identifies code in loops that has the same value for every
267 iteration of the loop, and move it to the loop pre-header.)");
268 printf(R"(
269 --loop-unroll
270 Fully unrolls loops marked with the Unroll flag)");
271 printf(R"(
272 --loop-unroll-partial
273 Partially unrolls loops marked with the Unroll flag. Takes an
274 additional non-0 integer argument to set the unroll factor, or
275 how many times a loop body should be duplicated)");
276 printf(R"(
277 --loop-peeling
278 Execute few first (respectively last) iterations before
279 (respectively after) the loop if it can elide some branches.)");
280 printf(R"(
281 --loop-peeling-threshold
282 Takes a non-0 integer argument to set the loop peeling code size
283 growth threshold. The threshold prevents the loop peeling
284 from happening if the code size increase created by
285 the optimization is above the threshold.)");
286 printf(R"(
287 --max-id-bound=<n>
288 Sets the maximum value for the id bound for the module. The
289 default is the minimum value for this limit, 0x3FFFFF. See
290 section 2.17 of the Spir-V specification.)");
291 printf(R"(
292 --merge-blocks
293 Join two blocks into a single block if the second has the
294 first as its only predecessor. Performed only on entry point
295 call tree functions.)");
296 printf(R"(
297 --merge-return
298 Changes functions that have multiple return statements so they
299 have a single return statement.
300
301 For structured control flow it is assumed that the only
302 unreachable blocks in the function are trivial merge and continue
303 blocks.
304
305 A trivial merge block contains the label and an OpUnreachable
306 instructions, nothing else. A trivial continue block contain a
307 label and an OpBranch to the header, nothing else.
308
309 These conditions are guaranteed to be met after running
310 dead-branch elimination.)");
311 printf(R"(
312 --loop-unswitch
313 Hoists loop-invariant conditionals out of loops by duplicating
314 the loop on each branch of the conditional and adjusting each
315 copy of the loop.)");
316 printf(R"(
317 -O
318 Optimize for performance. Apply a sequence of transformations
319 in an attempt to improve the performance of the generated
320 code. For this version of the optimizer, this flag is equivalent
321 to specifying the following optimization code names:
322 %s)",
323 GetOptimizationPasses().c_str());
324 printf(R"(
325 -Os
326 Optimize for size. Apply a sequence of transformations in an
327 attempt to minimize the size of the generated code. For this
328 version of the optimizer, this flag is equivalent to specifying
329 the following optimization code names:
330 %s
331
332 NOTE: The specific transformations done by -O and -Os change
333 from release to release.)",
334 GetSizePasses().c_str());
335 printf(R"(
336 -Oconfig=<file>
337 Apply the sequence of transformations indicated in <file>.
338 This file contains a sequence of strings separated by whitespace
339 (tabs, newlines or blanks). Each string is one of the flags
340 accepted by spirv-opt. Optimizations will be applied in the
341 sequence they appear in the file. This is equivalent to
342 specifying all the flags on the command line. For example,
343 given the file opts.cfg with the content:
344
345 --inline-entry-points-exhaustive
346 --eliminate-dead-code-aggressive
347
348 The following two invocations to spirv-opt are equivalent:
349
350 $ spirv-opt -Oconfig=opts.cfg program.spv
351
352 $ spirv-opt --inline-entry-points-exhaustive \
353 --eliminate-dead-code-aggressive program.spv
354
355 Lines starting with the character '#' in the configuration
356 file indicate a comment and will be ignored.
357
358 The -O, -Os, and -Oconfig flags act as macros. Using one of them
359 is equivalent to explicitly inserting the underlying flags at
360 that position in the command line. For example, the invocation
361 'spirv-opt --merge-blocks -O ...' applies the transformation
362 --merge-blocks followed by all the transformations implied by
363 -O.)");
364 printf(R"(
365 --preserve-bindings
366 Ensure that the optimizer preserves all bindings declared within
367 the module, even when those bindings are unused.)");
368 printf(R"(
369 --preserve-spec-constants
370 Ensure that the optimizer preserves all specialization constants declared
371 within the module, even when those constants are unused.)");
372 printf(R"(
373 --print-all
374 Print SPIR-V assembly to standard error output before each pass
375 and after the last pass.)");
376 printf(R"(
377 --private-to-local
378 Change the scope of private variables that are used in a single
379 function to that function.)");
380 printf(R"(
381 --reduce-load-size
382 Replaces loads of composite objects where not every component is
383 used by loads of just the elements that are used.)");
384 printf(R"(
385 --redundancy-elimination
386 Looks for instructions in the same function that compute the
387 same value, and deletes the redundant ones.)");
388 printf(R"(
389 --relax-block-layout
390 Forwards this option to the validator. See the validator help
391 for details.)");
392 printf(R"(
393 --relax-float-ops
394 Decorate all float operations with RelaxedPrecision if not already
395 so decorated. This does not decorate types or variables.)");
396 printf(R"(
397 --relax-logical-pointer
398 Forwards this option to the validator. See the validator help
399 for details.)");
400 printf(R"(
401 --relax-struct-store
402 Forwards this option to the validator. See the validator help
403 for details.)");
404 printf(R"(
405 --remove-duplicates
406 Removes duplicate types, decorations, capabilities and extension
407 instructions.)");
408 printf(R"(
409 --replace-invalid-opcode
410 Replaces instructions whose opcode is valid for shader modules,
411 but not for the current shader stage. To have an effect, all
412 entry points must have the same execution model.)");
413 printf(R"(
414 --ssa-rewrite
415 Replace loads and stores to function local variables with
416 operations on SSA IDs.)");
417 printf(R"(
418 --scalar-block-layout
419 Forwards this option to the validator. See the validator help
420 for details.)");
421 printf(R"(
422 --scalar-replacement[=<n>]
423 Replace aggregate function scope variables that are only accessed
424 via their elements with new function variables representing each
425 element. <n> is a limit on the size of the aggregates that will
426 be replaced. 0 means there is no limit. The default value is
427 100.)");
428 printf(R"(
429 --set-spec-const-default-value "<spec id>:<default value> ..."
430 Set the default values of the specialization constants with
431 <spec id>:<default value> pairs specified in a double-quoted
432 string. <spec id>:<default value> pairs must be separated by
433 blank spaces, and in each pair, spec id and default value must
434 be separated with colon ':' without any blank spaces in between.
435 e.g.: --set-spec-const-default-value "1:100 2:400")");
436 printf(R"(
437 --simplify-instructions
438 Will simplify all instructions in the function as much as
439 possible.)");
440 printf(R"(
441 --skip-block-layout
442 Forwards this option to the validator. See the validator help
443 for details.)");
444 printf(R"(
445 --skip-validation
446 Will not validate the SPIR-V before optimizing. If the SPIR-V
447 is invalid, the optimizer may fail or generate incorrect code.
448 This options should be used rarely, and with caution.)");
449 printf(R"(
450 --strength-reduction
451 Replaces instructions with equivalent and less expensive ones.)");
452 printf(R"(
453 --strip-atomic-counter-memory
454 Removes AtomicCountMemory bit from memory semantics values.)");
455 printf(R"(
456 --strip-debug
457 Remove all debug instructions.)");
458 printf(R"(
459 --strip-reflect
460 Remove all reflection information. For now, this covers
461 reflection information defined by SPV_GOOGLE_hlsl_functionality1
462 and SPV_KHR_non_semantic_info)");
463 printf(R"(
464 --target-env=<env>
465 Set the target environment. Without this flag the target
466 environment defaults to spv1.5. <env> must be one of
467 {%s})",
468 target_env_list.c_str());
469 printf(R"(
470 --time-report
471 Print the resource utilization of each pass (e.g., CPU time,
472 RSS) to standard error output. Currently it supports only Unix
473 systems. This option is the same as -ftime-report in GCC. It
474 prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
475 USR/SYS time are returned by getrusage() and can have a small
476 error.)");
477 printf(R"(
478 --upgrade-memory-model
479 Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
480 Transforms memory, image, atomic and barrier operations to conform
481 to that model's requirements.)");
482 printf(R"(
483 --vector-dce
484 This pass looks for components of vectors that are unused, and
485 removes them from the vector. Note this would still leave around
486 lots of dead code that a pass of ADCE will be able to remove.)");
487 printf(R"(
488 --workaround-1209
489 Rewrites instructions for which there are known driver bugs to
490 avoid triggering those bugs.
491 Current workarounds: Avoid OpUnreachable in loops.)");
492 printf(R"(
493 --workgroup-scalar-block-layout
494 Forwards this option to the validator. See the validator help
495 for details.)");
496 printf(R"(
497 --wrap-opkill
498 Replaces all OpKill instructions in functions that can be called
499 from a continue construct with a function call to a function
500 whose only instruction is an OpKill. This is done to enable
501 inlining on these functions.
502 )");
503 printf(R"(
504 --unify-const
505 Remove the duplicated constants.)");
506 printf(R"(
507 --validate-after-all
508 Validate the module after each pass is performed.)");
509 printf(R"(
510 -h, --help
511 Print this help.)");
512 printf(R"(
513 --version
514 Display optimizer version information.
515 )");
516 }
517
518 // Reads command-line flags the file specified in |oconfig_flag|. This string
519 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
520 // string and extracts the file name after the '=' sign.
521 //
522 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
523 //
524 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)525 bool ReadFlagsFromFile(const char* oconfig_flag,
526 std::vector<std::string>* file_flags) {
527 const char* fname = strchr(oconfig_flag, '=');
528 if (fname == nullptr || fname[0] != '=') {
529 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
530 oconfig_flag);
531 return false;
532 }
533 fname++;
534
535 std::ifstream input_file;
536 input_file.open(fname);
537 if (input_file.fail()) {
538 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
539 fname);
540 return false;
541 }
542
543 std::string line;
544 while (std::getline(input_file, line)) {
545 // Ignore empty lines and lines starting with the comment marker '#'.
546 if (line.length() == 0 || line[0] == '#') {
547 continue;
548 }
549
550 // Tokenize the line. Add all found tokens to the list of found flags. This
551 // mimics the way the shell will parse whitespace on the command line. NOTE:
552 // This does not support quoting and it is not intended to.
553 std::istringstream iss(line);
554 while (!iss.eof()) {
555 std::string flag;
556 iss >> flag;
557 file_flags->push_back(flag);
558 }
559 }
560
561 return true;
562 }
563
564 OptStatus ParseFlags(int argc, const char** argv,
565 spvtools::Optimizer* optimizer, const char** in_file,
566 const char** out_file,
567 spvtools::ValidatorOptions* validator_options,
568 spvtools::OptimizerOptions* optimizer_options);
569
570 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
571 // the spirv-opt binary (used to build a new argv vector for the recursive
572 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
573 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
574 // |optimizer_options| are as in ParseFlags.
575 //
576 // 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)577 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
578 spvtools::Optimizer* optimizer, const char** in_file,
579 const char** out_file,
580 spvtools::ValidatorOptions* validator_options,
581 spvtools::OptimizerOptions* optimizer_options) {
582 std::vector<std::string> flags;
583 flags.push_back(prog_name);
584
585 std::vector<std::string> file_flags;
586 if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
587 spvtools::Error(opt_diagnostic, nullptr, {},
588 "Could not read optimizer flags from configuration file");
589 return {OPT_STOP, 1};
590 }
591 flags.insert(flags.end(), file_flags.begin(), file_flags.end());
592
593 const char** new_argv = new const char*[flags.size()];
594 for (size_t i = 0; i < flags.size(); i++) {
595 if (flags[i].find("-Oconfig=") != std::string::npos) {
596 spvtools::Error(
597 opt_diagnostic, nullptr, {},
598 "Flag -Oconfig= may not be used inside the configuration file");
599 return {OPT_STOP, 1};
600 }
601 new_argv[i] = flags[i].c_str();
602 }
603
604 auto ret_val =
605 ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
606 out_file, validator_options, optimizer_options);
607 delete[] new_argv;
608 return ret_val;
609 }
610
611 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
612 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
613 // form '--pass_name=arg'. Since spirv-opt also accepts the other form, this
614 // function makes the necessary conversion.
615 //
616 // Pass flags that require additional arguments should be handled here. Note
617 // that additional arguments should be given as a single string. If the flag
618 // requires more than one argument, the pass creator in
619 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
620 // handler for --set-spec-const-default-value).
621 //
622 // If the argument requests one of the passes that need an additional argument,
623 // |argi| is modified to point past the current argument, and the string
624 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
625 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)626 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
627 const char* cur_arg = argv[*argi];
628 const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
629 std::ostringstream canonical_arg;
630 canonical_arg << cur_arg;
631
632 // NOTE: DO NOT ADD NEW FLAGS HERE.
633 //
634 // These flags are supported for backwards compatibility. When adding new
635 // passes that need extra arguments in its command-line flag, please make them
636 // use the syntax "--pass_name[=pass_arg].
637 if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
638 0 == strcmp(cur_arg, "--loop-fission") ||
639 0 == strcmp(cur_arg, "--loop-fusion") ||
640 0 == strcmp(cur_arg, "--loop-unroll-partial") ||
641 0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
642 if (next_arg) {
643 canonical_arg << "=" << next_arg;
644 ++(*argi);
645 }
646 }
647
648 return canonical_arg.str();
649 }
650
651 // Parses command-line flags. |argc| contains the number of command-line flags.
652 // |argv| points to an array of strings holding the flags. |optimizer| is the
653 // Optimizer instance used to optimize the program.
654 //
655 // On return, this function stores the name of the input program in |in_file|.
656 // The name of the output file in |out_file|. The return value indicates whether
657 // optimization should continue and a status code indicating an error or
658 // 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)659 OptStatus ParseFlags(int argc, const char** argv,
660 spvtools::Optimizer* optimizer, const char** in_file,
661 const char** out_file,
662 spvtools::ValidatorOptions* validator_options,
663 spvtools::OptimizerOptions* optimizer_options) {
664 std::vector<std::string> pass_flags;
665 for (int argi = 1; argi < argc; ++argi) {
666 const char* cur_arg = argv[argi];
667 if ('-' == cur_arg[0]) {
668 if (0 == strcmp(cur_arg, "--version")) {
669 spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
670 spvSoftwareVersionDetailsString());
671 return {OPT_STOP, 0};
672 } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
673 PrintUsage(argv[0]);
674 return {OPT_STOP, 0};
675 } else if (0 == strcmp(cur_arg, "-o")) {
676 if (!*out_file && argi + 1 < argc) {
677 *out_file = argv[++argi];
678 } else {
679 PrintUsage(argv[0]);
680 return {OPT_STOP, 1};
681 }
682 } else if ('\0' == cur_arg[1]) {
683 // Setting a filename of "-" to indicate stdin.
684 if (!*in_file) {
685 *in_file = cur_arg;
686 } else {
687 spvtools::Error(opt_diagnostic, nullptr, {},
688 "More than one input file specified");
689 return {OPT_STOP, 1};
690 }
691 } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
692 OptStatus status =
693 ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
694 validator_options, optimizer_options);
695 if (status.action != OPT_CONTINUE) {
696 return status;
697 }
698 } else if (0 == strcmp(cur_arg, "--skip-validation")) {
699 optimizer_options->set_run_validator(false);
700 } else if (0 == strcmp(cur_arg, "--print-all")) {
701 optimizer->SetPrintAll(&std::cerr);
702 } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
703 optimizer_options->set_preserve_bindings(true);
704 } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
705 optimizer_options->set_preserve_spec_constants(true);
706 } else if (0 == strcmp(cur_arg, "--time-report")) {
707 optimizer->SetTimeReport(&std::cerr);
708 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
709 validator_options->SetRelaxStructStore(true);
710 } else if (0 == strncmp(cur_arg, "--max-id-bound=",
711 sizeof("--max-id-bound=") - 1)) {
712 auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
713 // Will not allow values in the range [2^31,2^32).
714 uint32_t max_id_bound =
715 static_cast<uint32_t>(atoi(split_flag.second.c_str()));
716
717 // That SPIR-V mandates the minimum value for max id bound but
718 // implementations may allow higher minimum bounds.
719 if (max_id_bound < kDefaultMaxIdBound) {
720 spvtools::Error(opt_diagnostic, nullptr, {},
721 "The max id bound must be at least 0x3FFFFF");
722 return {OPT_STOP, 1};
723 }
724 optimizer_options->set_max_id_bound(max_id_bound);
725 validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
726 max_id_bound);
727 } else if (0 == strncmp(cur_arg,
728 "--target-env=", sizeof("--target-env=") - 1)) {
729 const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
730 const auto target_env_str = split_flag.second.c_str();
731 spv_target_env target_env;
732 if (!spvParseTargetEnv(target_env_str, &target_env)) {
733 spvtools::Error(opt_diagnostic, nullptr, {},
734 "Invalid value passed to --target-env");
735 return {OPT_STOP, 1};
736 }
737 optimizer->SetTargetEnv(target_env);
738 } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
739 optimizer->SetValidateAfterAll(true);
740 } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
741 validator_options->SetBeforeHlslLegalization(true);
742 } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
743 validator_options->SetRelaxLogicalPointer(true);
744 } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
745 validator_options->SetRelaxBlockLayout(true);
746 } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
747 validator_options->SetScalarBlockLayout(true);
748 } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
749 validator_options->SetWorkgroupScalarBlockLayout(true);
750 } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
751 validator_options->SetSkipBlockLayout(true);
752 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
753 validator_options->SetRelaxStructStore(true);
754 } else {
755 // Some passes used to accept the form '--pass arg', canonicalize them
756 // to '--pass=arg'.
757 pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
758
759 // If we were requested to legalize SPIR-V generated from the HLSL
760 // front-end, skip validation.
761 if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
762 validator_options->SetBeforeHlslLegalization(true);
763 }
764 }
765 } else {
766 if (!*in_file) {
767 *in_file = cur_arg;
768 } else {
769 spvtools::Error(opt_diagnostic, nullptr, {},
770 "More than one input file specified");
771 return {OPT_STOP, 1};
772 }
773 }
774 }
775
776 if (!optimizer->RegisterPassesFromFlags(pass_flags)) {
777 return {OPT_STOP, 1};
778 }
779
780 return {OPT_CONTINUE, 0};
781 }
782
783 } // namespace
784
main(int argc,const char ** argv)785 int main(int argc, const char** argv) {
786 const char* in_file = nullptr;
787 const char* out_file = nullptr;
788
789 spv_target_env target_env = kDefaultEnvironment;
790
791 spvtools::Optimizer optimizer(target_env);
792 optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
793
794 spvtools::ValidatorOptions validator_options;
795 spvtools::OptimizerOptions optimizer_options;
796 OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
797 &validator_options, &optimizer_options);
798 optimizer_options.set_validator_options(validator_options);
799
800 if (status.action == OPT_STOP) {
801 return status.code;
802 }
803
804 if (out_file == nullptr) {
805 spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
806 return 1;
807 }
808
809 std::vector<uint32_t> binary;
810 if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
811 return 1;
812 }
813
814 // By using the same vector as input and output, we save time in the case
815 // that there was no change.
816 bool ok =
817 optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
818
819 if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
820 return 1;
821 }
822
823 return ok ? 0 : 1;
824 }
825