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   --modify-maximal-reconvergence=[add|remove]
339                Add or remove the MaximallyReconvergesKHR execution mode to all
340                entry points in the module.
341                Note: when adding the execution mode, no attempt is made to
342                determine if any ray tracing repack instructions are used.)");
343   printf(R"(
344   --loop-unswitch
345                Hoists loop-invariant conditionals out of loops by duplicating
346                the loop on each branch of the conditional and adjusting each
347                copy of the loop.)");
348   printf(R"(
349   -O
350                Optimize for performance. Apply a sequence of transformations
351                in an attempt to improve the performance of the generated
352                code. For this version of the optimizer, this flag is equivalent
353                to specifying the following optimization code names:
354                %s)",
355          GetOptimizationPasses().c_str());
356   printf(R"(
357   -Os
358                Optimize for size. Apply a sequence of transformations in an
359                attempt to minimize the size of the generated code. For this
360                version of the optimizer, this flag is equivalent to specifying
361                the following optimization code names:
362                %s
363 
364                NOTE: The specific transformations done by -O and -Os change
365                      from release to release.)",
366          GetSizePasses().c_str());
367   printf(R"(
368   -Oconfig=<file>
369                Apply the sequence of transformations indicated in <file>.
370                This file contains a sequence of strings separated by whitespace
371                (tabs, newlines or blanks). Each string is one of the flags
372                accepted by spirv-opt. Optimizations will be applied in the
373                sequence they appear in the file. This is equivalent to
374                specifying all the flags on the command line. For example,
375                given the file opts.cfg with the content:
376 
377                 --inline-entry-points-exhaustive
378                 --eliminate-dead-code-aggressive
379 
380                The following two invocations to spirv-opt are equivalent:
381 
382                $ spirv-opt -Oconfig=opts.cfg program.spv
383 
384                $ spirv-opt --inline-entry-points-exhaustive \
385                     --eliminate-dead-code-aggressive program.spv
386 
387                Lines starting with the character '#' in the configuration
388                file indicate a comment and will be ignored.
389 
390                The -O, -Os, and -Oconfig flags act as macros. Using one of them
391                is equivalent to explicitly inserting the underlying flags at
392                that position in the command line. For example, the invocation
393                'spirv-opt --merge-blocks -O ...' applies the transformation
394                --merge-blocks followed by all the transformations implied by
395                -O.)");
396   printf(R"(
397   --preserve-bindings
398                Ensure that the optimizer preserves all bindings declared within
399                the module, even when those bindings are unused.)");
400   printf(R"(
401   --preserve-interface
402                Ensure that input and output variables are not removed from the
403                shader, even if they are unused. Note that this option applies to
404                all passes that will be run regardless of the order of the flags.)");
405   printf(R"(
406   --preserve-spec-constants
407                Ensure that the optimizer preserves all specialization constants declared
408                within the module, even when those constants are unused.)");
409   printf(R"(
410   --print-all
411                Print SPIR-V assembly to standard error output before each pass
412                and after the last pass.)");
413   printf(R"(
414   --private-to-local
415                Change the scope of private variables that are used in a single
416                function to that function.)");
417   printf(R"(
418   --reduce-load-size[=<threshold>]
419                Replaces loads of composite objects where not every component is
420                used by loads of just the elements that are used.  If the ratio
421                of the used components of the load is less than the <threshold>,
422                we replace the load.  <threshold> is a double type number.  If
423                it is bigger than 1.0, we always replaces the load.)");
424   printf(R"(
425   --redundancy-elimination
426                Looks for instructions in the same function that compute the
427                same value, and deletes the redundant ones.)");
428   printf(R"(
429   --relax-block-layout
430                Forwards this option to the validator.  See the validator help
431                for details.)");
432   printf(R"(
433   --relax-float-ops
434                Decorate all float operations with RelaxedPrecision if not already
435                so decorated. This does not decorate types or variables.)");
436   printf(R"(
437   --relax-logical-pointer
438                Forwards this option to the validator.  See the validator help
439                for details.)");
440   printf(R"(
441   --relax-struct-store
442                Forwards this option to the validator.  See the validator help
443                for details.)");
444   printf(R"(
445   --remove-duplicates
446                Removes duplicate types, decorations, capabilities and extension
447                instructions.)");
448   printf(R"(
449   --remove-unused-interface-variables
450                Removes variables referenced on the |OpEntryPoint| instruction
451                that are not referenced in the entry point function or any function
452                in its call tree.  Note that this could cause the shader interface
453                to no longer match other shader stages.)");
454   printf(R"(
455   --replace-invalid-opcode
456                Replaces instructions whose opcode is valid for shader modules,
457                but not for the current shader stage.  To have an effect, all
458                entry points must have the same execution model.)");
459   printf(R"(
460   --ssa-rewrite
461                Replace loads and stores to function local variables with
462                operations on SSA IDs.)");
463   printf(R"(
464   --scalar-block-layout
465                Forwards this option to the validator.  See the validator help
466                for details.)");
467   printf(R"(
468   --scalar-replacement[=<n>]
469                Replace aggregate function scope variables that are only accessed
470                via their elements with new function variables representing each
471                element.  <n> is a limit on the size of the aggregates that will
472                be replaced.  0 means there is no limit.  The default value is
473                100.)");
474   printf(R"(
475   --set-spec-const-default-value "<spec id>:<default value> ..."
476                Set the default values of the specialization constants with
477                <spec id>:<default value> pairs specified in a double-quoted
478                string. <spec id>:<default value> pairs must be separated by
479                blank spaces, and in each pair, spec id and default value must
480                be separated with colon ':' without any blank spaces in between.
481                e.g.: --set-spec-const-default-value "1:100 2:400")");
482   printf(R"(
483   --simplify-instructions
484                Will simplify all instructions in the function as much as
485                possible.)");
486   printf(R"(
487   --skip-block-layout
488                Forwards this option to the validator.  See the validator help
489                for details.)");
490   printf(R"(
491   --skip-validation
492                Will not validate the SPIR-V before optimizing.  If the SPIR-V
493                is invalid, the optimizer may fail or generate incorrect code.
494                This options should be used rarely, and with caution.)");
495   printf(R"(
496   --strength-reduction
497                Replaces instructions with equivalent and less expensive ones.)");
498   printf(R"(
499   --strip-debug
500                Remove all debug instructions.)");
501   printf(R"(
502   --strip-nonsemantic
503                Remove all reflection and nonsemantic information.)");
504   printf(R"(
505   --strip-reflect
506                DEPRECATED.  Remove all reflection information.  For now, this
507                covers reflection information defined by
508                SPV_GOOGLE_hlsl_functionality1 and SPV_KHR_non_semantic_info)");
509   printf(R"(
510   --switch-descriptorset=<from>:<to>
511                Switch any DescriptoSet decorations using the value <from> to
512                the new value <to>.)");
513   printf(R"(
514   --target-env=<env>
515                Set the target environment. Without this flag the target
516                environment defaults to spv1.5. <env> must be one of
517                {%s})",
518          target_env_list.c_str());
519   printf(R"(
520   --time-report
521                Print the resource utilization of each pass (e.g., CPU time,
522                RSS) to standard error output. Currently it supports only Unix
523                systems. This option is the same as -ftime-report in GCC. It
524                prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
525                USR/SYS time are returned by getrusage() and can have a small
526                error.)");
527   printf(R"(
528   --trim-capabilities
529                Remove unnecessary capabilities and extensions declared within the
530                module.)");
531   printf(R"(
532   --upgrade-memory-model
533                Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
534                Transforms memory, image, atomic and barrier operations to conform
535                to that model's requirements.)");
536   printf(R"(
537   --vector-dce
538                This pass looks for components of vectors that are unused, and
539                removes them from the vector.  Note this would still leave around
540                lots of dead code that a pass of ADCE will be able to remove.)");
541   printf(R"(
542   --workaround-1209
543                Rewrites instructions for which there are known driver bugs to
544                avoid triggering those bugs.
545                Current workarounds: Avoid OpUnreachable in loops.)");
546   printf(R"(
547   --workgroup-scalar-block-layout
548                Forwards this option to the validator.  See the validator help
549                for details.)");
550   printf(R"(
551   --wrap-opkill
552                Replaces all OpKill instructions in functions that can be called
553                from a continue construct with a function call to a function
554                whose only instruction is an OpKill.  This is done to enable
555                inlining on these functions.
556                )");
557   printf(R"(
558   --unify-const
559                Remove the duplicated constants.)");
560   printf(R"(
561   --validate-after-all
562                Validate the module after each pass is performed.)");
563   printf(R"(
564   -h, --help
565                Print this help.)");
566   printf(R"(
567   --version
568                Display optimizer version information.
569 )");
570 }
571 
572 // Reads command-line flags  the file specified in |oconfig_flag|. This string
573 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
574 // string and extracts the file name after the '=' sign.
575 //
576 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
577 //
578 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)579 bool ReadFlagsFromFile(const char* oconfig_flag,
580                        std::vector<std::string>* file_flags) {
581   const char* fname = strchr(oconfig_flag, '=');
582   if (fname == nullptr || fname[0] != '=') {
583     spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
584                      oconfig_flag);
585     return false;
586   }
587   fname++;
588 
589   std::ifstream input_file;
590   input_file.open(fname);
591   if (input_file.fail()) {
592     spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
593                      fname);
594     return false;
595   }
596 
597   std::string line;
598   while (std::getline(input_file, line)) {
599     // Ignore empty lines and lines starting with the comment marker '#'.
600     if (line.length() == 0 || line[0] == '#') {
601       continue;
602     }
603 
604     // Tokenize the line.  Add all found tokens to the list of found flags. This
605     // mimics the way the shell will parse whitespace on the command line. NOTE:
606     // This does not support quoting and it is not intended to.
607     std::istringstream iss(line);
608     while (!iss.eof()) {
609       std::string flag;
610       iss >> flag;
611       file_flags->push_back(flag);
612     }
613   }
614 
615   return true;
616 }
617 
618 OptStatus ParseFlags(int argc, const char** argv,
619                      spvtools::Optimizer* optimizer, const char** in_file,
620                      const char** out_file,
621                      spvtools::ValidatorOptions* validator_options,
622                      spvtools::OptimizerOptions* optimizer_options);
623 
624 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
625 // the spirv-opt binary (used to build a new argv vector for the recursive
626 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
627 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
628 // |optimizer_options| are as in ParseFlags.
629 //
630 // 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)631 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
632                            spvtools::Optimizer* optimizer, const char** in_file,
633                            const char** out_file,
634                            spvtools::ValidatorOptions* validator_options,
635                            spvtools::OptimizerOptions* optimizer_options) {
636   std::vector<std::string> flags;
637   flags.push_back(prog_name);
638 
639   std::vector<std::string> file_flags;
640   if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
641     spvtools::Error(opt_diagnostic, nullptr, {},
642                     "Could not read optimizer flags from configuration file");
643     return {OPT_STOP, 1};
644   }
645   flags.insert(flags.end(), file_flags.begin(), file_flags.end());
646 
647   const char** new_argv = new const char*[flags.size()];
648   for (size_t i = 0; i < flags.size(); i++) {
649     if (flags[i].find("-Oconfig=") != std::string::npos) {
650       spvtools::Error(
651           opt_diagnostic, nullptr, {},
652           "Flag -Oconfig= may not be used inside the configuration file");
653       return {OPT_STOP, 1};
654     }
655     new_argv[i] = flags[i].c_str();
656   }
657 
658   auto ret_val =
659       ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
660                  out_file, validator_options, optimizer_options);
661   delete[] new_argv;
662   return ret_val;
663 }
664 
665 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
666 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
667 // form '--pass_name=arg'.  Since spirv-opt also accepts the other form, this
668 // function makes the necessary conversion.
669 //
670 // Pass flags that require additional arguments should be handled here.  Note
671 // that additional arguments should be given as a single string.  If the flag
672 // requires more than one argument, the pass creator in
673 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
674 // handler for --set-spec-const-default-value).
675 //
676 // If the argument requests one of the passes that need an additional argument,
677 // |argi| is modified to point past the current argument, and the string
678 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
679 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)680 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
681   const char* cur_arg = argv[*argi];
682   const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
683   std::ostringstream canonical_arg;
684   canonical_arg << cur_arg;
685 
686   // NOTE: DO NOT ADD NEW FLAGS HERE.
687   //
688   // These flags are supported for backwards compatibility.  When adding new
689   // passes that need extra arguments in its command-line flag, please make them
690   // use the syntax "--pass_name[=pass_arg].
691   if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
692       0 == strcmp(cur_arg, "--loop-fission") ||
693       0 == strcmp(cur_arg, "--loop-fusion") ||
694       0 == strcmp(cur_arg, "--loop-unroll-partial") ||
695       0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
696     if (next_arg) {
697       canonical_arg << "=" << next_arg;
698       ++(*argi);
699     }
700   }
701 
702   return canonical_arg.str();
703 }
704 
705 // Parses command-line flags. |argc| contains the number of command-line flags.
706 // |argv| points to an array of strings holding the flags. |optimizer| is the
707 // Optimizer instance used to optimize the program.
708 //
709 // On return, this function stores the name of the input program in |in_file|.
710 // The name of the output file in |out_file|. The return value indicates whether
711 // optimization should continue and a status code indicating an error or
712 // 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)713 OptStatus ParseFlags(int argc, const char** argv,
714                      spvtools::Optimizer* optimizer, const char** in_file,
715                      const char** out_file,
716                      spvtools::ValidatorOptions* validator_options,
717                      spvtools::OptimizerOptions* optimizer_options) {
718   std::vector<std::string> pass_flags;
719   bool preserve_interface = true;
720   for (int argi = 1; argi < argc; ++argi) {
721     const char* cur_arg = argv[argi];
722     if ('-' == cur_arg[0]) {
723       if (0 == strcmp(cur_arg, "--version")) {
724         spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
725                        spvSoftwareVersionDetailsString());
726         return {OPT_STOP, 0};
727       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
728         PrintUsage(argv[0]);
729         return {OPT_STOP, 0};
730       } else if (0 == strcmp(cur_arg, "-o")) {
731         if (!*out_file && argi + 1 < argc) {
732           *out_file = argv[++argi];
733         } else {
734           PrintUsage(argv[0]);
735           return {OPT_STOP, 1};
736         }
737       } else if ('\0' == cur_arg[1]) {
738         // Setting a filename of "-" to indicate stdin.
739         if (!*in_file) {
740           *in_file = cur_arg;
741         } else {
742           spvtools::Error(opt_diagnostic, nullptr, {},
743                           "More than one input file specified");
744           return {OPT_STOP, 1};
745         }
746       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
747         OptStatus status =
748             ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
749                              validator_options, optimizer_options);
750         if (status.action != OPT_CONTINUE) {
751           return status;
752         }
753       } else if (0 == strcmp(cur_arg, "--skip-validation")) {
754         optimizer_options->set_run_validator(false);
755       } else if (0 == strcmp(cur_arg, "--print-all")) {
756         optimizer->SetPrintAll(&std::cerr);
757       } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
758         optimizer_options->set_preserve_bindings(true);
759       } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
760         optimizer_options->set_preserve_spec_constants(true);
761       } else if (0 == strcmp(cur_arg, "--time-report")) {
762         optimizer->SetTimeReport(&std::cerr);
763       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
764         validator_options->SetRelaxStructStore(true);
765       } else if (0 == strncmp(cur_arg, "--max-id-bound=",
766                               sizeof("--max-id-bound=") - 1)) {
767         auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
768         // Will not allow values in the range [2^31,2^32).
769         uint32_t max_id_bound =
770             static_cast<uint32_t>(atoi(split_flag.second.c_str()));
771 
772         // That SPIR-V mandates the minimum value for max id bound but
773         // implementations may allow higher minimum bounds.
774         if (max_id_bound < kDefaultMaxIdBound) {
775           spvtools::Error(opt_diagnostic, nullptr, {},
776                           "The max id bound must be at least 0x3FFFFF");
777           return {OPT_STOP, 1};
778         }
779         optimizer_options->set_max_id_bound(max_id_bound);
780         validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
781                                              max_id_bound);
782       } else if (0 == strncmp(cur_arg,
783                               "--target-env=", sizeof("--target-env=") - 1)) {
784         const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
785         const auto target_env_str = split_flag.second.c_str();
786         spv_target_env target_env;
787         if (!spvParseTargetEnv(target_env_str, &target_env)) {
788           spvtools::Error(opt_diagnostic, nullptr, {},
789                           "Invalid value passed to --target-env");
790           return {OPT_STOP, 1};
791         }
792         optimizer->SetTargetEnv(target_env);
793       } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
794         optimizer->SetValidateAfterAll(true);
795       } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
796         validator_options->SetBeforeHlslLegalization(true);
797       } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
798         validator_options->SetRelaxLogicalPointer(true);
799       } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
800         validator_options->SetRelaxBlockLayout(true);
801       } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
802         validator_options->SetScalarBlockLayout(true);
803       } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
804         validator_options->SetWorkgroupScalarBlockLayout(true);
805       } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
806         validator_options->SetSkipBlockLayout(true);
807       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
808         validator_options->SetRelaxStructStore(true);
809       } else if (0 == strcmp(cur_arg, "--preserve-interface")) {
810         preserve_interface = true;
811       } else {
812         // Some passes used to accept the form '--pass arg', canonicalize them
813         // to '--pass=arg'.
814         pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
815 
816         // If we were requested to legalize SPIR-V generated from the HLSL
817         // front-end, skip validation.
818         if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
819           validator_options->SetBeforeHlslLegalization(true);
820         }
821       }
822     } else {
823       if (!*in_file) {
824         *in_file = cur_arg;
825       } else {
826         spvtools::Error(opt_diagnostic, nullptr, {},
827                         "More than one input file specified");
828         return {OPT_STOP, 1};
829       }
830     }
831   }
832 
833   if (!optimizer->RegisterPassesFromFlags(pass_flags, preserve_interface)) {
834     return {OPT_STOP, 1};
835   }
836 
837   return {OPT_CONTINUE, 0};
838 }
839 
840 }  // namespace
841 
main(int argc,const char ** argv)842 int main(int argc, const char** argv) {
843   const char* in_file = nullptr;
844   const char* out_file = nullptr;
845 
846   spv_target_env target_env = kDefaultEnvironment;
847 
848   spvtools::Optimizer optimizer(target_env);
849   optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
850 
851   spvtools::ValidatorOptions validator_options;
852   spvtools::OptimizerOptions optimizer_options;
853   OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
854                                 &validator_options, &optimizer_options);
855   optimizer_options.set_validator_options(validator_options);
856 
857   if (status.action == OPT_STOP) {
858     return status.code;
859   }
860 
861   if (out_file == nullptr) {
862     spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
863     return 1;
864   }
865 
866   std::vector<uint32_t> binary;
867   if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
868     return 1;
869   }
870 
871   // By using the same vector as input and output, we save time in the case
872   // that there was no change.
873   bool ok =
874       optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
875 
876   if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
877     return 1;
878   }
879 
880   return ok ? 0 : 1;
881 }
882