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