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