• 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   --switch-descriptorset=<from>:<to>
500                Switch any DescriptoSet decorations using the value <from> to
501                the new value <to>.)");
502   printf(R"(
503   --target-env=<env>
504                Set the target environment. Without this flag the target
505                environment defaults to spv1.5. <env> must be one of
506                {%s})",
507          target_env_list.c_str());
508   printf(R"(
509   --time-report
510                Print the resource utilization of each pass (e.g., CPU time,
511                RSS) to standard error output. Currently it supports only Unix
512                systems. This option is the same as -ftime-report in GCC. It
513                prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
514                USR/SYS time are returned by getrusage() and can have a small
515                error.)");
516   printf(R"(
517   --upgrade-memory-model
518                Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
519                Transforms memory, image, atomic and barrier operations to conform
520                to that model's requirements.)");
521   printf(R"(
522   --vector-dce
523                This pass looks for components of vectors that are unused, and
524                removes them from the vector.  Note this would still leave around
525                lots of dead code that a pass of ADCE will be able to remove.)");
526   printf(R"(
527   --workaround-1209
528                Rewrites instructions for which there are known driver bugs to
529                avoid triggering those bugs.
530                Current workarounds: Avoid OpUnreachable in loops.)");
531   printf(R"(
532   --workgroup-scalar-block-layout
533                Forwards this option to the validator.  See the validator help
534                for details.)");
535   printf(R"(
536   --wrap-opkill
537                Replaces all OpKill instructions in functions that can be called
538                from a continue construct with a function call to a function
539                whose only instruction is an OpKill.  This is done to enable
540                inlining on these functions.
541                )");
542   printf(R"(
543   --unify-const
544                Remove the duplicated constants.)");
545   printf(R"(
546   --validate-after-all
547                Validate the module after each pass is performed.)");
548   printf(R"(
549   -h, --help
550                Print this help.)");
551   printf(R"(
552   --version
553                Display optimizer version information.
554 )");
555 }
556 
557 // Reads command-line flags  the file specified in |oconfig_flag|. This string
558 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
559 // string and extracts the file name after the '=' sign.
560 //
561 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
562 //
563 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)564 bool ReadFlagsFromFile(const char* oconfig_flag,
565                        std::vector<std::string>* file_flags) {
566   const char* fname = strchr(oconfig_flag, '=');
567   if (fname == nullptr || fname[0] != '=') {
568     spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
569                      oconfig_flag);
570     return false;
571   }
572   fname++;
573 
574   std::ifstream input_file;
575   input_file.open(fname);
576   if (input_file.fail()) {
577     spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
578                      fname);
579     return false;
580   }
581 
582   std::string line;
583   while (std::getline(input_file, line)) {
584     // Ignore empty lines and lines starting with the comment marker '#'.
585     if (line.length() == 0 || line[0] == '#') {
586       continue;
587     }
588 
589     // Tokenize the line.  Add all found tokens to the list of found flags. This
590     // mimics the way the shell will parse whitespace on the command line. NOTE:
591     // This does not support quoting and it is not intended to.
592     std::istringstream iss(line);
593     while (!iss.eof()) {
594       std::string flag;
595       iss >> flag;
596       file_flags->push_back(flag);
597     }
598   }
599 
600   return true;
601 }
602 
603 OptStatus ParseFlags(int argc, const char** argv,
604                      spvtools::Optimizer* optimizer, const char** in_file,
605                      const char** out_file,
606                      spvtools::ValidatorOptions* validator_options,
607                      spvtools::OptimizerOptions* optimizer_options);
608 
609 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
610 // the spirv-opt binary (used to build a new argv vector for the recursive
611 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
612 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
613 // |optimizer_options| are as in ParseFlags.
614 //
615 // 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)616 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
617                            spvtools::Optimizer* optimizer, const char** in_file,
618                            const char** out_file,
619                            spvtools::ValidatorOptions* validator_options,
620                            spvtools::OptimizerOptions* optimizer_options) {
621   std::vector<std::string> flags;
622   flags.push_back(prog_name);
623 
624   std::vector<std::string> file_flags;
625   if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
626     spvtools::Error(opt_diagnostic, nullptr, {},
627                     "Could not read optimizer flags from configuration file");
628     return {OPT_STOP, 1};
629   }
630   flags.insert(flags.end(), file_flags.begin(), file_flags.end());
631 
632   const char** new_argv = new const char*[flags.size()];
633   for (size_t i = 0; i < flags.size(); i++) {
634     if (flags[i].find("-Oconfig=") != std::string::npos) {
635       spvtools::Error(
636           opt_diagnostic, nullptr, {},
637           "Flag -Oconfig= may not be used inside the configuration file");
638       return {OPT_STOP, 1};
639     }
640     new_argv[i] = flags[i].c_str();
641   }
642 
643   auto ret_val =
644       ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
645                  out_file, validator_options, optimizer_options);
646   delete[] new_argv;
647   return ret_val;
648 }
649 
650 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
651 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
652 // form '--pass_name=arg'.  Since spirv-opt also accepts the other form, this
653 // function makes the necessary conversion.
654 //
655 // Pass flags that require additional arguments should be handled here.  Note
656 // that additional arguments should be given as a single string.  If the flag
657 // requires more than one argument, the pass creator in
658 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
659 // handler for --set-spec-const-default-value).
660 //
661 // If the argument requests one of the passes that need an additional argument,
662 // |argi| is modified to point past the current argument, and the string
663 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
664 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)665 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
666   const char* cur_arg = argv[*argi];
667   const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
668   std::ostringstream canonical_arg;
669   canonical_arg << cur_arg;
670 
671   // NOTE: DO NOT ADD NEW FLAGS HERE.
672   //
673   // These flags are supported for backwards compatibility.  When adding new
674   // passes that need extra arguments in its command-line flag, please make them
675   // use the syntax "--pass_name[=pass_arg].
676   if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
677       0 == strcmp(cur_arg, "--loop-fission") ||
678       0 == strcmp(cur_arg, "--loop-fusion") ||
679       0 == strcmp(cur_arg, "--loop-unroll-partial") ||
680       0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
681     if (next_arg) {
682       canonical_arg << "=" << next_arg;
683       ++(*argi);
684     }
685   }
686 
687   return canonical_arg.str();
688 }
689 
690 // Parses command-line flags. |argc| contains the number of command-line flags.
691 // |argv| points to an array of strings holding the flags. |optimizer| is the
692 // Optimizer instance used to optimize the program.
693 //
694 // On return, this function stores the name of the input program in |in_file|.
695 // The name of the output file in |out_file|. The return value indicates whether
696 // optimization should continue and a status code indicating an error or
697 // 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)698 OptStatus ParseFlags(int argc, const char** argv,
699                      spvtools::Optimizer* optimizer, const char** in_file,
700                      const char** out_file,
701                      spvtools::ValidatorOptions* validator_options,
702                      spvtools::OptimizerOptions* optimizer_options) {
703   std::vector<std::string> pass_flags;
704   for (int argi = 1; argi < argc; ++argi) {
705     const char* cur_arg = argv[argi];
706     if ('-' == cur_arg[0]) {
707       if (0 == strcmp(cur_arg, "--version")) {
708         spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
709                        spvSoftwareVersionDetailsString());
710         return {OPT_STOP, 0};
711       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
712         PrintUsage(argv[0]);
713         return {OPT_STOP, 0};
714       } else if (0 == strcmp(cur_arg, "-o")) {
715         if (!*out_file && argi + 1 < argc) {
716           *out_file = argv[++argi];
717         } else {
718           PrintUsage(argv[0]);
719           return {OPT_STOP, 1};
720         }
721       } else if ('\0' == cur_arg[1]) {
722         // Setting a filename of "-" to indicate stdin.
723         if (!*in_file) {
724           *in_file = cur_arg;
725         } else {
726           spvtools::Error(opt_diagnostic, nullptr, {},
727                           "More than one input file specified");
728           return {OPT_STOP, 1};
729         }
730       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
731         OptStatus status =
732             ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
733                              validator_options, optimizer_options);
734         if (status.action != OPT_CONTINUE) {
735           return status;
736         }
737       } else if (0 == strcmp(cur_arg, "--skip-validation")) {
738         optimizer_options->set_run_validator(false);
739       } else if (0 == strcmp(cur_arg, "--print-all")) {
740         optimizer->SetPrintAll(&std::cerr);
741       } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
742         optimizer_options->set_preserve_bindings(true);
743       } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
744         optimizer_options->set_preserve_spec_constants(true);
745       } else if (0 == strcmp(cur_arg, "--time-report")) {
746         optimizer->SetTimeReport(&std::cerr);
747       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
748         validator_options->SetRelaxStructStore(true);
749       } else if (0 == strncmp(cur_arg, "--max-id-bound=",
750                               sizeof("--max-id-bound=") - 1)) {
751         auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
752         // Will not allow values in the range [2^31,2^32).
753         uint32_t max_id_bound =
754             static_cast<uint32_t>(atoi(split_flag.second.c_str()));
755 
756         // That SPIR-V mandates the minimum value for max id bound but
757         // implementations may allow higher minimum bounds.
758         if (max_id_bound < kDefaultMaxIdBound) {
759           spvtools::Error(opt_diagnostic, nullptr, {},
760                           "The max id bound must be at least 0x3FFFFF");
761           return {OPT_STOP, 1};
762         }
763         optimizer_options->set_max_id_bound(max_id_bound);
764         validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
765                                              max_id_bound);
766       } else if (0 == strncmp(cur_arg,
767                               "--target-env=", sizeof("--target-env=") - 1)) {
768         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
769         const auto target_env_str = split_flag.second.c_str();
770         spv_target_env target_env;
771         if (!spvParseTargetEnv(target_env_str, &target_env)) {
772           spvtools::Error(opt_diagnostic, nullptr, {},
773                           "Invalid value passed to --target-env");
774           return {OPT_STOP, 1};
775         }
776         optimizer->SetTargetEnv(target_env);
777       } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
778         optimizer->SetValidateAfterAll(true);
779       } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
780         validator_options->SetBeforeHlslLegalization(true);
781       } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
782         validator_options->SetRelaxLogicalPointer(true);
783       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
784         validator_options->SetRelaxBlockLayout(true);
785       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
786         validator_options->SetScalarBlockLayout(true);
787       } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
788         validator_options->SetWorkgroupScalarBlockLayout(true);
789       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
790         validator_options->SetSkipBlockLayout(true);
791       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
792         validator_options->SetRelaxStructStore(true);
793       } else {
794         // Some passes used to accept the form '--pass arg', canonicalize them
795         // to '--pass=arg'.
796         pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
797 
798         // If we were requested to legalize SPIR-V generated from the HLSL
799         // front-end, skip validation.
800         if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
801           validator_options->SetBeforeHlslLegalization(true);
802         }
803       }
804     } else {
805       if (!*in_file) {
806         *in_file = cur_arg;
807       } else {
808         spvtools::Error(opt_diagnostic, nullptr, {},
809                         "More than one input file specified");
810         return {OPT_STOP, 1};
811       }
812     }
813   }
814 
815   if (!optimizer->RegisterPassesFromFlags(pass_flags)) {
816     return {OPT_STOP, 1};
817   }
818 
819   return {OPT_CONTINUE, 0};
820 }
821 
822 }  // namespace
823 
main(int argc,const char ** argv)824 int main(int argc, const char** argv) {
825   const char* in_file = nullptr;
826   const char* out_file = nullptr;
827 
828   spv_target_env target_env = kDefaultEnvironment;
829 
830   spvtools::Optimizer optimizer(target_env);
831   optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
832 
833   spvtools::ValidatorOptions validator_options;
834   spvtools::OptimizerOptions optimizer_options;
835   OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
836                                 &validator_options, &optimizer_options);
837   optimizer_options.set_validator_options(validator_options);
838 
839   if (status.action == OPT_STOP) {
840     return status.code;
841   }
842 
843   if (out_file == nullptr) {
844     spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
845     return 1;
846   }
847 
848   std::vector<uint32_t> binary;
849   if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
850     return 1;
851   }
852 
853   // By using the same vector as input and output, we save time in the case
854   // that there was no change.
855   bool ok =
856       optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
857 
858   if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
859     return 1;
860   }
861 
862   return ok ? 0 : 1;
863 }
864