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