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