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