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