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