1 // Copyright (c) 2016 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <algorithm>
16 #include <cassert>
17 #include <cstring>
18 #include <fstream>
19 #include <iostream>
20 #include <memory>
21 #include <sstream>
22 #include <string>
23 #include <vector>
24
25 #include "source/opt/log.h"
26 #include "source/spirv_target_env.h"
27 #include "source/util/string_utils.h"
28 #include "spirv-tools/libspirv.hpp"
29 #include "spirv-tools/optimizer.hpp"
30 #include "tools/io.h"
31 #include "tools/util/cli_consumer.h"
32
33 namespace {
34
35 // Status and actions to perform after parsing command-line arguments.
36 enum OptActions { OPT_CONTINUE, OPT_STOP };
37
38 struct OptStatus {
39 OptActions action;
40 int code;
41 };
42
43 // Message consumer for this tool. Used to emit diagnostics during
44 // initialization and setup. Note that |source| and |position| are irrelevant
45 // here because we are still not processing a SPIR-V input file.
opt_diagnostic(spv_message_level_t level,const char *,const spv_position_t &,const char * message)46 void opt_diagnostic(spv_message_level_t level, const char* /*source*/,
47 const spv_position_t& /*position*/, const char* message) {
48 if (level == SPV_MSG_ERROR) {
49 fprintf(stderr, "error: ");
50 }
51 fprintf(stderr, "%s\n", message);
52 }
53
GetListOfPassesAsString(const spvtools::Optimizer & optimizer)54 std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) {
55 std::stringstream ss;
56 for (const auto& name : optimizer.GetPassNames()) {
57 ss << "\n\t\t" << name;
58 }
59 return ss.str();
60 }
61
62 const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_6;
63
GetLegalizationPasses()64 std::string GetLegalizationPasses() {
65 spvtools::Optimizer optimizer(kDefaultEnvironment);
66 optimizer.RegisterLegalizationPasses();
67 return GetListOfPassesAsString(optimizer);
68 }
69
GetOptimizationPasses()70 std::string GetOptimizationPasses() {
71 spvtools::Optimizer optimizer(kDefaultEnvironment);
72 optimizer.RegisterPerformancePasses();
73 return GetListOfPassesAsString(optimizer);
74 }
75
GetSizePasses()76 std::string GetSizePasses() {
77 spvtools::Optimizer optimizer(kDefaultEnvironment);
78 optimizer.RegisterSizePasses();
79 return GetListOfPassesAsString(optimizer);
80 }
81
PrintUsage(const char * program)82 void PrintUsage(const char* program) {
83 std::string target_env_list = spvTargetEnvList(16, 80);
84 // NOTE: Please maintain flags in lexicographical order.
85 printf(
86 R"(%s - Optimize a SPIR-V binary file.
87
88 USAGE: %s [options] [<input>] -o <output>
89
90 The SPIR-V binary is read from <input>. If no file is specified,
91 or if <input> is "-", then the binary is read from standard input.
92 if <output> is "-", then the optimized output is written to
93 standard output.
94
95 NOTE: The optimizer is a work in progress.
96
97 Options (in lexicographical order):)",
98 program, program);
99 printf(R"(
100 --amd-ext-to-khr
101 Replaces the extensions VK_AMD_shader_ballot, VK_AMD_gcn_shader,
102 and VK_AMD_shader_trinary_minmax with equivalent code using core
103 instructions and capabilities.)");
104 printf(R"(
105 --before-hlsl-legalization
106 Forwards this option to the validator. See the validator help
107 for details.)");
108 printf(R"(
109 --ccp
110 Apply the conditional constant propagation transform. This will
111 propagate constant values throughout the program, and simplify
112 expressions and conditional jumps with known predicate
113 values. Performed on entry point call tree functions and
114 exported functions.)");
115 printf(R"(
116 --cfg-cleanup
117 Cleanup the control flow graph. This will remove any unnecessary
118 code from the CFG like unreachable code. Performed on entry
119 point call tree functions and exported functions.)");
120 printf(R"(
121 --combine-access-chains
122 Combines chained access chains to produce a single instruction
123 where possible.)");
124 printf(R"(
125 --compact-ids
126 Remap result ids to a compact range starting from %%1 and without
127 any gaps.)");
128 printf(R"(
129 --convert-local-access-chains
130 Convert constant index access chain loads/stores into
131 equivalent load/stores with inserts and extracts. Performed
132 on function scope variables referenced only with load, store,
133 and constant index access chains in entry point call tree
134 functions.)");
135 printf(R"(
136 --convert-relaxed-to-half
137 Convert all RelaxedPrecision arithmetic operations to half
138 precision, inserting conversion operations where needed.
139 Run after function scope variable load and store elimination
140 for better results. Simplify-instructions, redundancy-elimination
141 and DCE should be run after this pass to eliminate excess
142 conversions. This conversion is useful when the target platform
143 does not support RelaxedPrecision or ignores it. This pass also
144 removes all RelaxedPrecision decorations.)");
145 printf(R"(
146 --convert-to-sampled-image "<descriptor set>:<binding> ..."
147 convert images and/or samplers with the given pairs of descriptor
148 set and binding to sampled images. If a pair of an image and a
149 sampler have the same pair of descriptor set and binding that is
150 one of the given pairs, they will be converted to a sampled
151 image. In addition, if only an image or a sampler has the
152 descriptor set and binding that is one of the given pairs, it
153 will be converted to a sampled image.)");
154 printf(R"(
155 --copy-propagate-arrays
156 Does propagation of memory references when an array is a copy of
157 another. It will only propagate an array if the source is never
158 written to, and the only store to the target is the copy.)");
159 printf(R"(
160 --replace-desc-array-access-using-var-index
161 Replaces accesses to descriptor arrays based on a variable index
162 with a switch that has a case for every possible value of the
163 index.)");
164 printf(R"(
165 --spread-volatile-semantics
166 Spread Volatile semantics to variables with SMIDNV, WarpIDNV,
167 SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
168 SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask
169 BuiltIn decorations or OpLoad for them when the shader model is
170 ray generation, closest hit, miss, intersection, or callable.
171 For the SPIR-V version is 1.6 or above, it also spreads Volatile
172 semantics to a variable with HelperInvocation BuiltIn decoration
173 in the fragement shader.)");
174 printf(R"(
175 --descriptor-scalar-replacement
176 Replaces every array variable |desc| that has a DescriptorSet
177 and Binding decorations with a new variable for each element of
178 the array. Suppose |desc| was bound at binding |b|. Then the
179 variable corresponding to |desc[i]| will have binding |b+i|.
180 The descriptor set will be the same. All accesses to |desc|
181 must be in OpAccessChain instructions with a literal index for
182 the first index.)");
183 printf(R"(
184 --eliminate-dead-branches
185 Convert conditional branches with constant condition to the
186 indicated unconditional branch. Delete all resulting dead
187 code. Performed only on entry point call tree functions.)");
188 printf(R"(
189 --eliminate-dead-code-aggressive
190 Delete instructions which do not contribute to a function's
191 output. Performed only on entry point call tree functions.)");
192 printf(R"(
193 --eliminate-dead-const
194 Eliminate dead constants.)");
195 printf(R"(
196 --eliminate-dead-functions
197 Deletes functions that cannot be reached from entry points or
198 exported functions.)");
199 printf(R"(
200 --eliminate-dead-inserts
201 Deletes unreferenced inserts into composites, most notably
202 unused stores to vector components, that are not removed by
203 aggressive dead code elimination.)");
204 printf(R"(
205 --eliminate-dead-input-components
206 Deletes unused components from input variables. Currently
207 deletes trailing unused elements from input arrays.)");
208 printf(R"(
209 --eliminate-dead-variables
210 Deletes module scope variables that are not referenced.)");
211 printf(R"(
212 --eliminate-insert-extract
213 DEPRECATED. This pass has been replaced by the simplification
214 pass, and that pass will be run instead.
215 See --simplify-instructions.)");
216 printf(R"(
217 --eliminate-local-multi-store
218 Replace stores and loads of function scope variables that are
219 stored multiple times. Performed on variables referenceed only
220 with loads and stores. Performed only on entry point call tree
221 functions.)");
222 printf(R"(
223 --eliminate-local-single-block
224 Perform single-block store/load and load/load elimination.
225 Performed only on function scope variables in entry point
226 call tree functions.)");
227 printf(R"(
228 --eliminate-local-single-store
229 Replace stores and loads of function scope variables that are
230 only stored once. Performed on variables referenceed only with
231 loads and stores. Performed only on entry point call tree
232 functions.)");
233 printf(R"(
234 --fix-func-call-param
235 fix non memory argument for the function call, replace
236 accesschain pointer argument with a variable.)");
237 printf(R"(
238 --flatten-decorations
239 Replace decoration groups with repeated OpDecorate and
240 OpMemberDecorate instructions.)");
241 printf(R"(
242 --fold-spec-const-op-composite
243 Fold the spec constants defined by OpSpecConstantOp or
244 OpSpecConstantComposite instructions to front-end constants
245 when possible.)");
246 printf(R"(
247 --freeze-spec-const
248 Freeze the values of specialization constants to their default
249 values.)");
250 printf(R"(
251 --graphics-robust-access
252 Clamp indices used to access buffers and internal composite
253 values, providing guarantees that satisfy Vulkan's
254 robustBufferAccess rules.)");
255 printf(R"(
256 --if-conversion
257 Convert if-then-else like assignments into OpSelect.)");
258 printf(R"(
259 --inline-entry-points-exhaustive
260 Exhaustively inline all function calls in entry point call tree
261 functions. Currently does not inline calls to functions with
262 early return in a loop.)");
263 printf(R"(
264 --legalize-hlsl
265 Runs a series of optimizations that attempts to take SPIR-V
266 generated by an HLSL front-end and generates legal Vulkan SPIR-V.
267 The optimizations are:
268 %s
269
270 Note this does not guarantee legal code. This option passes the
271 option --relax-logical-pointer to the validator.)",
272 GetLegalizationPasses().c_str());
273 printf(R"(
274 --local-redundancy-elimination
275 Looks for instructions in the same basic block that compute the
276 same value, and deletes the redundant ones.)");
277 printf(R"(
278 --loop-fission
279 Splits any top level loops in which the register pressure has
280 exceeded a given threshold. The threshold must follow the use of
281 this flag and must be a positive integer value.)");
282 printf(R"(
283 --loop-fusion
284 Identifies adjacent loops with the same lower and upper bound.
285 If this is legal, then merge the loops into a single loop.
286 Includes heuristics to ensure it does not increase number of
287 registers too much, while reducing the number of loads from
288 memory. Takes an additional positive integer argument to set
289 the maximum number of registers.)");
290 printf(R"(
291 --loop-invariant-code-motion
292 Identifies code in loops that has the same value for every
293 iteration of the loop, and move it to the loop pre-header.)");
294 printf(R"(
295 --loop-unroll
296 Fully unrolls loops marked with the Unroll flag)");
297 printf(R"(
298 --loop-unroll-partial
299 Partially unrolls loops marked with the Unroll flag. Takes an
300 additional non-0 integer argument to set the unroll factor, or
301 how many times a loop body should be duplicated)");
302 printf(R"(
303 --loop-peeling
304 Execute few first (respectively last) iterations before
305 (respectively after) the loop if it can elide some branches.)");
306 printf(R"(
307 --loop-peeling-threshold
308 Takes a non-0 integer argument to set the loop peeling code size
309 growth threshold. The threshold prevents the loop peeling
310 from happening if the code size increase created by
311 the optimization is above the threshold.)");
312 printf(R"(
313 --max-id-bound=<n>
314 Sets the maximum value for the id bound for the module. The
315 default is the minimum value for this limit, 0x3FFFFF. See
316 section 2.17 of the Spir-V specification.)");
317 printf(R"(
318 --merge-blocks
319 Join two blocks into a single block if the second has the
320 first as its only predecessor. Performed only on entry point
321 call tree functions.)");
322 printf(R"(
323 --merge-return
324 Changes functions that have multiple return statements so they
325 have a single return statement.
326
327 For structured control flow it is assumed that the only
328 unreachable blocks in the function are trivial merge and continue
329 blocks.
330
331 A trivial merge block contains the label and an OpUnreachable
332 instructions, nothing else. A trivial continue block contain a
333 label and an OpBranch to the header, nothing else.
334
335 These conditions are guaranteed to be met after running
336 dead-branch elimination.)");
337 printf(R"(
338 --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 --target-env=<env>
500 Set the target environment. Without this flag the target
501 environment defaults to spv1.5. <env> must be one of
502 {%s})",
503 target_env_list.c_str());
504 printf(R"(
505 --time-report
506 Print the resource utilization of each pass (e.g., CPU time,
507 RSS) to standard error output. Currently it supports only Unix
508 systems. This option is the same as -ftime-report in GCC. It
509 prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
510 USR/SYS time are returned by getrusage() and can have a small
511 error.)");
512 printf(R"(
513 --upgrade-memory-model
514 Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
515 Transforms memory, image, atomic and barrier operations to conform
516 to that model's requirements.)");
517 printf(R"(
518 --vector-dce
519 This pass looks for components of vectors that are unused, and
520 removes them from the vector. Note this would still leave around
521 lots of dead code that a pass of ADCE will be able to remove.)");
522 printf(R"(
523 --workaround-1209
524 Rewrites instructions for which there are known driver bugs to
525 avoid triggering those bugs.
526 Current workarounds: Avoid OpUnreachable in loops.)");
527 printf(R"(
528 --workgroup-scalar-block-layout
529 Forwards this option to the validator. See the validator help
530 for details.)");
531 printf(R"(
532 --wrap-opkill
533 Replaces all OpKill instructions in functions that can be called
534 from a continue construct with a function call to a function
535 whose only instruction is an OpKill. This is done to enable
536 inlining on these functions.
537 )");
538 printf(R"(
539 --unify-const
540 Remove the duplicated constants.)");
541 printf(R"(
542 --validate-after-all
543 Validate the module after each pass is performed.)");
544 printf(R"(
545 -h, --help
546 Print this help.)");
547 printf(R"(
548 --version
549 Display optimizer version information.
550 )");
551 }
552
553 // Reads command-line flags the file specified in |oconfig_flag|. This string
554 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
555 // string and extracts the file name after the '=' sign.
556 //
557 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
558 //
559 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)560 bool ReadFlagsFromFile(const char* oconfig_flag,
561 std::vector<std::string>* file_flags) {
562 const char* fname = strchr(oconfig_flag, '=');
563 if (fname == nullptr || fname[0] != '=') {
564 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
565 oconfig_flag);
566 return false;
567 }
568 fname++;
569
570 std::ifstream input_file;
571 input_file.open(fname);
572 if (input_file.fail()) {
573 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
574 fname);
575 return false;
576 }
577
578 std::string line;
579 while (std::getline(input_file, line)) {
580 // Ignore empty lines and lines starting with the comment marker '#'.
581 if (line.length() == 0 || line[0] == '#') {
582 continue;
583 }
584
585 // Tokenize the line. Add all found tokens to the list of found flags. This
586 // mimics the way the shell will parse whitespace on the command line. NOTE:
587 // This does not support quoting and it is not intended to.
588 std::istringstream iss(line);
589 while (!iss.eof()) {
590 std::string flag;
591 iss >> flag;
592 file_flags->push_back(flag);
593 }
594 }
595
596 return true;
597 }
598
599 OptStatus ParseFlags(int argc, const char** argv,
600 spvtools::Optimizer* optimizer, const char** in_file,
601 const char** out_file,
602 spvtools::ValidatorOptions* validator_options,
603 spvtools::OptimizerOptions* optimizer_options);
604
605 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
606 // the spirv-opt binary (used to build a new argv vector for the recursive
607 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
608 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
609 // |optimizer_options| are as in ParseFlags.
610 //
611 // 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)612 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
613 spvtools::Optimizer* optimizer, const char** in_file,
614 const char** out_file,
615 spvtools::ValidatorOptions* validator_options,
616 spvtools::OptimizerOptions* optimizer_options) {
617 std::vector<std::string> flags;
618 flags.push_back(prog_name);
619
620 std::vector<std::string> file_flags;
621 if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
622 spvtools::Error(opt_diagnostic, nullptr, {},
623 "Could not read optimizer flags from configuration file");
624 return {OPT_STOP, 1};
625 }
626 flags.insert(flags.end(), file_flags.begin(), file_flags.end());
627
628 const char** new_argv = new const char*[flags.size()];
629 for (size_t i = 0; i < flags.size(); i++) {
630 if (flags[i].find("-Oconfig=") != std::string::npos) {
631 spvtools::Error(
632 opt_diagnostic, nullptr, {},
633 "Flag -Oconfig= may not be used inside the configuration file");
634 return {OPT_STOP, 1};
635 }
636 new_argv[i] = flags[i].c_str();
637 }
638
639 auto ret_val =
640 ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
641 out_file, validator_options, optimizer_options);
642 delete[] new_argv;
643 return ret_val;
644 }
645
646 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
647 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
648 // form '--pass_name=arg'. Since spirv-opt also accepts the other form, this
649 // function makes the necessary conversion.
650 //
651 // Pass flags that require additional arguments should be handled here. Note
652 // that additional arguments should be given as a single string. If the flag
653 // requires more than one argument, the pass creator in
654 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
655 // handler for --set-spec-const-default-value).
656 //
657 // If the argument requests one of the passes that need an additional argument,
658 // |argi| is modified to point past the current argument, and the string
659 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
660 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)661 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
662 const char* cur_arg = argv[*argi];
663 const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
664 std::ostringstream canonical_arg;
665 canonical_arg << cur_arg;
666
667 // NOTE: DO NOT ADD NEW FLAGS HERE.
668 //
669 // These flags are supported for backwards compatibility. When adding new
670 // passes that need extra arguments in its command-line flag, please make them
671 // use the syntax "--pass_name[=pass_arg].
672 if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
673 0 == strcmp(cur_arg, "--loop-fission") ||
674 0 == strcmp(cur_arg, "--loop-fusion") ||
675 0 == strcmp(cur_arg, "--loop-unroll-partial") ||
676 0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
677 if (next_arg) {
678 canonical_arg << "=" << next_arg;
679 ++(*argi);
680 }
681 }
682
683 return canonical_arg.str();
684 }
685
686 // Parses command-line flags. |argc| contains the number of command-line flags.
687 // |argv| points to an array of strings holding the flags. |optimizer| is the
688 // Optimizer instance used to optimize the program.
689 //
690 // On return, this function stores the name of the input program in |in_file|.
691 // The name of the output file in |out_file|. The return value indicates whether
692 // optimization should continue and a status code indicating an error or
693 // 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)694 OptStatus ParseFlags(int argc, const char** argv,
695 spvtools::Optimizer* optimizer, const char** in_file,
696 const char** out_file,
697 spvtools::ValidatorOptions* validator_options,
698 spvtools::OptimizerOptions* optimizer_options) {
699 std::vector<std::string> pass_flags;
700 for (int argi = 1; argi < argc; ++argi) {
701 const char* cur_arg = argv[argi];
702 if ('-' == cur_arg[0]) {
703 if (0 == strcmp(cur_arg, "--version")) {
704 spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
705 spvSoftwareVersionDetailsString());
706 return {OPT_STOP, 0};
707 } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
708 PrintUsage(argv[0]);
709 return {OPT_STOP, 0};
710 } else if (0 == strcmp(cur_arg, "-o")) {
711 if (!*out_file && argi + 1 < argc) {
712 *out_file = argv[++argi];
713 } else {
714 PrintUsage(argv[0]);
715 return {OPT_STOP, 1};
716 }
717 } else if ('\0' == cur_arg[1]) {
718 // Setting a filename of "-" to indicate stdin.
719 if (!*in_file) {
720 *in_file = cur_arg;
721 } else {
722 spvtools::Error(opt_diagnostic, nullptr, {},
723 "More than one input file specified");
724 return {OPT_STOP, 1};
725 }
726 } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
727 OptStatus status =
728 ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
729 validator_options, optimizer_options);
730 if (status.action != OPT_CONTINUE) {
731 return status;
732 }
733 } else if (0 == strcmp(cur_arg, "--skip-validation")) {
734 optimizer_options->set_run_validator(false);
735 } else if (0 == strcmp(cur_arg, "--print-all")) {
736 optimizer->SetPrintAll(&std::cerr);
737 } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
738 optimizer_options->set_preserve_bindings(true);
739 } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
740 optimizer_options->set_preserve_spec_constants(true);
741 } else if (0 == strcmp(cur_arg, "--time-report")) {
742 optimizer->SetTimeReport(&std::cerr);
743 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
744 validator_options->SetRelaxStructStore(true);
745 } else if (0 == strncmp(cur_arg, "--max-id-bound=",
746 sizeof("--max-id-bound=") - 1)) {
747 auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
748 // Will not allow values in the range [2^31,2^32).
749 uint32_t max_id_bound =
750 static_cast<uint32_t>(atoi(split_flag.second.c_str()));
751
752 // That SPIR-V mandates the minimum value for max id bound but
753 // implementations may allow higher minimum bounds.
754 if (max_id_bound < kDefaultMaxIdBound) {
755 spvtools::Error(opt_diagnostic, nullptr, {},
756 "The max id bound must be at least 0x3FFFFF");
757 return {OPT_STOP, 1};
758 }
759 optimizer_options->set_max_id_bound(max_id_bound);
760 validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
761 max_id_bound);
762 } else if (0 == strncmp(cur_arg,
763 "--target-env=", sizeof("--target-env=") - 1)) {
764 const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
765 const auto target_env_str = split_flag.second.c_str();
766 spv_target_env target_env;
767 if (!spvParseTargetEnv(target_env_str, &target_env)) {
768 spvtools::Error(opt_diagnostic, nullptr, {},
769 "Invalid value passed to --target-env");
770 return {OPT_STOP, 1};
771 }
772 optimizer->SetTargetEnv(target_env);
773 } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
774 optimizer->SetValidateAfterAll(true);
775 } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
776 validator_options->SetBeforeHlslLegalization(true);
777 } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
778 validator_options->SetRelaxLogicalPointer(true);
779 } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
780 validator_options->SetRelaxBlockLayout(true);
781 } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
782 validator_options->SetScalarBlockLayout(true);
783 } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
784 validator_options->SetWorkgroupScalarBlockLayout(true);
785 } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
786 validator_options->SetSkipBlockLayout(true);
787 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
788 validator_options->SetRelaxStructStore(true);
789 } else {
790 // Some passes used to accept the form '--pass arg', canonicalize them
791 // to '--pass=arg'.
792 pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
793
794 // If we were requested to legalize SPIR-V generated from the HLSL
795 // front-end, skip validation.
796 if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
797 validator_options->SetBeforeHlslLegalization(true);
798 }
799 }
800 } else {
801 if (!*in_file) {
802 *in_file = cur_arg;
803 } else {
804 spvtools::Error(opt_diagnostic, nullptr, {},
805 "More than one input file specified");
806 return {OPT_STOP, 1};
807 }
808 }
809 }
810
811 if (!optimizer->RegisterPassesFromFlags(pass_flags)) {
812 return {OPT_STOP, 1};
813 }
814
815 return {OPT_CONTINUE, 0};
816 }
817
818 } // namespace
819
main(int argc,const char ** argv)820 int main(int argc, const char** argv) {
821 const char* in_file = nullptr;
822 const char* out_file = nullptr;
823
824 spv_target_env target_env = kDefaultEnvironment;
825
826 spvtools::Optimizer optimizer(target_env);
827 optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
828
829 spvtools::ValidatorOptions validator_options;
830 spvtools::OptimizerOptions optimizer_options;
831 OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
832 &validator_options, &optimizer_options);
833 optimizer_options.set_validator_options(validator_options);
834
835 if (status.action == OPT_STOP) {
836 return status.code;
837 }
838
839 if (out_file == nullptr) {
840 spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
841 return 1;
842 }
843
844 std::vector<uint32_t> binary;
845 if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
846 return 1;
847 }
848
849 // By using the same vector as input and output, we save time in the case
850 // that there was no change.
851 bool ok =
852 optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
853
854 if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
855 return 1;
856 }
857
858 return ok ? 0 : 1;
859 }
860