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 --modify-maximal-reconvergence=[add|remove]
339 Add or remove the MaximallyReconvergesKHR execution mode to all
340 entry points in the module.
341 Note: when adding the execution mode, no attempt is made to
342 determine if any ray tracing repack instructions are used.)");
343 printf(R"(
344 --loop-unswitch
345 Hoists loop-invariant conditionals out of loops by duplicating
346 the loop on each branch of the conditional and adjusting each
347 copy of the loop.)");
348 printf(R"(
349 -O
350 Optimize for performance. Apply a sequence of transformations
351 in an attempt to improve the performance of the generated
352 code. For this version of the optimizer, this flag is equivalent
353 to specifying the following optimization code names:
354 %s)",
355 GetOptimizationPasses().c_str());
356 printf(R"(
357 -Os
358 Optimize for size. Apply a sequence of transformations in an
359 attempt to minimize the size of the generated code. For this
360 version of the optimizer, this flag is equivalent to specifying
361 the following optimization code names:
362 %s
363
364 NOTE: The specific transformations done by -O and -Os change
365 from release to release.)",
366 GetSizePasses().c_str());
367 printf(R"(
368 -Oconfig=<file>
369 Apply the sequence of transformations indicated in <file>.
370 This file contains a sequence of strings separated by whitespace
371 (tabs, newlines or blanks). Each string is one of the flags
372 accepted by spirv-opt. Optimizations will be applied in the
373 sequence they appear in the file. This is equivalent to
374 specifying all the flags on the command line. For example,
375 given the file opts.cfg with the content:
376
377 --inline-entry-points-exhaustive
378 --eliminate-dead-code-aggressive
379
380 The following two invocations to spirv-opt are equivalent:
381
382 $ spirv-opt -Oconfig=opts.cfg program.spv
383
384 $ spirv-opt --inline-entry-points-exhaustive \
385 --eliminate-dead-code-aggressive program.spv
386
387 Lines starting with the character '#' in the configuration
388 file indicate a comment and will be ignored.
389
390 The -O, -Os, and -Oconfig flags act as macros. Using one of them
391 is equivalent to explicitly inserting the underlying flags at
392 that position in the command line. For example, the invocation
393 'spirv-opt --merge-blocks -O ...' applies the transformation
394 --merge-blocks followed by all the transformations implied by
395 -O.)");
396 printf(R"(
397 --preserve-bindings
398 Ensure that the optimizer preserves all bindings declared within
399 the module, even when those bindings are unused.)");
400 printf(R"(
401 --preserve-interface
402 Ensure that input and output variables are not removed from the
403 shader, even if they are unused. Note that this option applies to
404 all passes that will be run regardless of the order of the flags.)");
405 printf(R"(
406 --preserve-spec-constants
407 Ensure that the optimizer preserves all specialization constants declared
408 within the module, even when those constants are unused.)");
409 printf(R"(
410 --print-all
411 Print SPIR-V assembly to standard error output before each pass
412 and after the last pass.)");
413 printf(R"(
414 --private-to-local
415 Change the scope of private variables that are used in a single
416 function to that function.)");
417 printf(R"(
418 --reduce-load-size[=<threshold>]
419 Replaces loads of composite objects where not every component is
420 used by loads of just the elements that are used. If the ratio
421 of the used components of the load is less than the <threshold>,
422 we replace the load. <threshold> is a double type number. If
423 it is bigger than 1.0, we always replaces the load.)");
424 printf(R"(
425 --redundancy-elimination
426 Looks for instructions in the same function that compute the
427 same value, and deletes the redundant ones.)");
428 printf(R"(
429 --relax-block-layout
430 Forwards this option to the validator. See the validator help
431 for details.)");
432 printf(R"(
433 --relax-float-ops
434 Decorate all float operations with RelaxedPrecision if not already
435 so decorated. This does not decorate types or variables.)");
436 printf(R"(
437 --relax-logical-pointer
438 Forwards this option to the validator. See the validator help
439 for details.)");
440 printf(R"(
441 --relax-struct-store
442 Forwards this option to the validator. See the validator help
443 for details.)");
444 printf(R"(
445 --remove-duplicates
446 Removes duplicate types, decorations, capabilities and extension
447 instructions.)");
448 printf(R"(
449 --remove-unused-interface-variables
450 Removes variables referenced on the |OpEntryPoint| instruction
451 that are not referenced in the entry point function or any function
452 in its call tree. Note that this could cause the shader interface
453 to no longer match other shader stages.)");
454 printf(R"(
455 --replace-invalid-opcode
456 Replaces instructions whose opcode is valid for shader modules,
457 but not for the current shader stage. To have an effect, all
458 entry points must have the same execution model.)");
459 printf(R"(
460 --ssa-rewrite
461 Replace loads and stores to function local variables with
462 operations on SSA IDs.)");
463 printf(R"(
464 --scalar-block-layout
465 Forwards this option to the validator. See the validator help
466 for details.)");
467 printf(R"(
468 --scalar-replacement[=<n>]
469 Replace aggregate function scope variables that are only accessed
470 via their elements with new function variables representing each
471 element. <n> is a limit on the size of the aggregates that will
472 be replaced. 0 means there is no limit. The default value is
473 100.)");
474 printf(R"(
475 --set-spec-const-default-value "<spec id>:<default value> ..."
476 Set the default values of the specialization constants with
477 <spec id>:<default value> pairs specified in a double-quoted
478 string. <spec id>:<default value> pairs must be separated by
479 blank spaces, and in each pair, spec id and default value must
480 be separated with colon ':' without any blank spaces in between.
481 e.g.: --set-spec-const-default-value "1:100 2:400")");
482 printf(R"(
483 --simplify-instructions
484 Will simplify all instructions in the function as much as
485 possible.)");
486 printf(R"(
487 --skip-block-layout
488 Forwards this option to the validator. See the validator help
489 for details.)");
490 printf(R"(
491 --skip-validation
492 Will not validate the SPIR-V before optimizing. If the SPIR-V
493 is invalid, the optimizer may fail or generate incorrect code.
494 This options should be used rarely, and with caution.)");
495 printf(R"(
496 --strength-reduction
497 Replaces instructions with equivalent and less expensive ones.)");
498 printf(R"(
499 --strip-debug
500 Remove all debug instructions.)");
501 printf(R"(
502 --strip-nonsemantic
503 Remove all reflection and nonsemantic information.)");
504 printf(R"(
505 --strip-reflect
506 DEPRECATED. Remove all reflection information. For now, this
507 covers reflection information defined by
508 SPV_GOOGLE_hlsl_functionality1 and SPV_KHR_non_semantic_info)");
509 printf(R"(
510 --switch-descriptorset=<from>:<to>
511 Switch any DescriptoSet decorations using the value <from> to
512 the new value <to>.)");
513 printf(R"(
514 --target-env=<env>
515 Set the target environment. Without this flag the target
516 environment defaults to spv1.5. <env> must be one of
517 {%s})",
518 target_env_list.c_str());
519 printf(R"(
520 --time-report
521 Print the resource utilization of each pass (e.g., CPU time,
522 RSS) to standard error output. Currently it supports only Unix
523 systems. This option is the same as -ftime-report in GCC. It
524 prints CPU/WALL/USR/SYS time (and RSS if possible), but note that
525 USR/SYS time are returned by getrusage() and can have a small
526 error.)");
527 printf(R"(
528 --trim-capabilities
529 Remove unnecessary capabilities and extensions declared within the
530 module.)");
531 printf(R"(
532 --upgrade-memory-model
533 Upgrades the Logical GLSL450 memory model to Logical VulkanKHR.
534 Transforms memory, image, atomic and barrier operations to conform
535 to that model's requirements.)");
536 printf(R"(
537 --vector-dce
538 This pass looks for components of vectors that are unused, and
539 removes them from the vector. Note this would still leave around
540 lots of dead code that a pass of ADCE will be able to remove.)");
541 printf(R"(
542 --workaround-1209
543 Rewrites instructions for which there are known driver bugs to
544 avoid triggering those bugs.
545 Current workarounds: Avoid OpUnreachable in loops.)");
546 printf(R"(
547 --workgroup-scalar-block-layout
548 Forwards this option to the validator. See the validator help
549 for details.)");
550 printf(R"(
551 --wrap-opkill
552 Replaces all OpKill instructions in functions that can be called
553 from a continue construct with a function call to a function
554 whose only instruction is an OpKill. This is done to enable
555 inlining on these functions.
556 )");
557 printf(R"(
558 --unify-const
559 Remove the duplicated constants.)");
560 printf(R"(
561 --validate-after-all
562 Validate the module after each pass is performed.)");
563 printf(R"(
564 -h, --help
565 Print this help.)");
566 printf(R"(
567 --version
568 Display optimizer version information.
569 )");
570 }
571
572 // Reads command-line flags the file specified in |oconfig_flag|. This string
573 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
574 // string and extracts the file name after the '=' sign.
575 //
576 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
577 //
578 // This function returns true on success, false on failure.
ReadFlagsFromFile(const char * oconfig_flag,std::vector<std::string> * file_flags)579 bool ReadFlagsFromFile(const char* oconfig_flag,
580 std::vector<std::string>* file_flags) {
581 const char* fname = strchr(oconfig_flag, '=');
582 if (fname == nullptr || fname[0] != '=') {
583 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Invalid -Oconfig flag %s",
584 oconfig_flag);
585 return false;
586 }
587 fname++;
588
589 std::ifstream input_file;
590 input_file.open(fname);
591 if (input_file.fail()) {
592 spvtools::Errorf(opt_diagnostic, nullptr, {}, "Could not open file '%s'",
593 fname);
594 return false;
595 }
596
597 std::string line;
598 while (std::getline(input_file, line)) {
599 // Ignore empty lines and lines starting with the comment marker '#'.
600 if (line.length() == 0 || line[0] == '#') {
601 continue;
602 }
603
604 // Tokenize the line. Add all found tokens to the list of found flags. This
605 // mimics the way the shell will parse whitespace on the command line. NOTE:
606 // This does not support quoting and it is not intended to.
607 std::istringstream iss(line);
608 while (!iss.eof()) {
609 std::string flag;
610 iss >> flag;
611 file_flags->push_back(flag);
612 }
613 }
614
615 return true;
616 }
617
618 OptStatus ParseFlags(int argc, const char** argv,
619 spvtools::Optimizer* optimizer, const char** in_file,
620 const char** out_file,
621 spvtools::ValidatorOptions* validator_options,
622 spvtools::OptimizerOptions* optimizer_options);
623
624 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
625 // the spirv-opt binary (used to build a new argv vector for the recursive
626 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
627 // |optimizer|, |in_file|, |out_file|, |validator_options|, and
628 // |optimizer_options| are as in ParseFlags.
629 //
630 // 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)631 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
632 spvtools::Optimizer* optimizer, const char** in_file,
633 const char** out_file,
634 spvtools::ValidatorOptions* validator_options,
635 spvtools::OptimizerOptions* optimizer_options) {
636 std::vector<std::string> flags;
637 flags.push_back(prog_name);
638
639 std::vector<std::string> file_flags;
640 if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
641 spvtools::Error(opt_diagnostic, nullptr, {},
642 "Could not read optimizer flags from configuration file");
643 return {OPT_STOP, 1};
644 }
645 flags.insert(flags.end(), file_flags.begin(), file_flags.end());
646
647 const char** new_argv = new const char*[flags.size()];
648 for (size_t i = 0; i < flags.size(); i++) {
649 if (flags[i].find("-Oconfig=") != std::string::npos) {
650 spvtools::Error(
651 opt_diagnostic, nullptr, {},
652 "Flag -Oconfig= may not be used inside the configuration file");
653 return {OPT_STOP, 1};
654 }
655 new_argv[i] = flags[i].c_str();
656 }
657
658 auto ret_val =
659 ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer, in_file,
660 out_file, validator_options, optimizer_options);
661 delete[] new_argv;
662 return ret_val;
663 }
664
665 // Canonicalize the flag in |argv[argi]| of the form '--pass arg' into
666 // '--pass=arg'. The optimizer only accepts arguments to pass names that use the
667 // form '--pass_name=arg'. Since spirv-opt also accepts the other form, this
668 // function makes the necessary conversion.
669 //
670 // Pass flags that require additional arguments should be handled here. Note
671 // that additional arguments should be given as a single string. If the flag
672 // requires more than one argument, the pass creator in
673 // Optimizer::GetPassFromFlag() should parse it accordingly (e.g., see the
674 // handler for --set-spec-const-default-value).
675 //
676 // If the argument requests one of the passes that need an additional argument,
677 // |argi| is modified to point past the current argument, and the string
678 // "argv[argi]=argv[argi + 1]" is returned. Otherwise, |argi| is unmodified and
679 // the string "|argv[argi]|" is returned.
CanonicalizeFlag(const char ** argv,int argc,int * argi)680 std::string CanonicalizeFlag(const char** argv, int argc, int* argi) {
681 const char* cur_arg = argv[*argi];
682 const char* next_arg = (*argi + 1 < argc) ? argv[*argi + 1] : nullptr;
683 std::ostringstream canonical_arg;
684 canonical_arg << cur_arg;
685
686 // NOTE: DO NOT ADD NEW FLAGS HERE.
687 //
688 // These flags are supported for backwards compatibility. When adding new
689 // passes that need extra arguments in its command-line flag, please make them
690 // use the syntax "--pass_name[=pass_arg].
691 if (0 == strcmp(cur_arg, "--set-spec-const-default-value") ||
692 0 == strcmp(cur_arg, "--loop-fission") ||
693 0 == strcmp(cur_arg, "--loop-fusion") ||
694 0 == strcmp(cur_arg, "--loop-unroll-partial") ||
695 0 == strcmp(cur_arg, "--loop-peeling-threshold")) {
696 if (next_arg) {
697 canonical_arg << "=" << next_arg;
698 ++(*argi);
699 }
700 }
701
702 return canonical_arg.str();
703 }
704
705 // Parses command-line flags. |argc| contains the number of command-line flags.
706 // |argv| points to an array of strings holding the flags. |optimizer| is the
707 // Optimizer instance used to optimize the program.
708 //
709 // On return, this function stores the name of the input program in |in_file|.
710 // The name of the output file in |out_file|. The return value indicates whether
711 // optimization should continue and a status code indicating an error or
712 // 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)713 OptStatus ParseFlags(int argc, const char** argv,
714 spvtools::Optimizer* optimizer, const char** in_file,
715 const char** out_file,
716 spvtools::ValidatorOptions* validator_options,
717 spvtools::OptimizerOptions* optimizer_options) {
718 std::vector<std::string> pass_flags;
719 bool preserve_interface = true;
720 for (int argi = 1; argi < argc; ++argi) {
721 const char* cur_arg = argv[argi];
722 if ('-' == cur_arg[0]) {
723 if (0 == strcmp(cur_arg, "--version")) {
724 spvtools::Logf(opt_diagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
725 spvSoftwareVersionDetailsString());
726 return {OPT_STOP, 0};
727 } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
728 PrintUsage(argv[0]);
729 return {OPT_STOP, 0};
730 } else if (0 == strcmp(cur_arg, "-o")) {
731 if (!*out_file && argi + 1 < argc) {
732 *out_file = argv[++argi];
733 } else {
734 PrintUsage(argv[0]);
735 return {OPT_STOP, 1};
736 }
737 } else if ('\0' == cur_arg[1]) {
738 // Setting a filename of "-" to indicate stdin.
739 if (!*in_file) {
740 *in_file = cur_arg;
741 } else {
742 spvtools::Error(opt_diagnostic, nullptr, {},
743 "More than one input file specified");
744 return {OPT_STOP, 1};
745 }
746 } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
747 OptStatus status =
748 ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file,
749 validator_options, optimizer_options);
750 if (status.action != OPT_CONTINUE) {
751 return status;
752 }
753 } else if (0 == strcmp(cur_arg, "--skip-validation")) {
754 optimizer_options->set_run_validator(false);
755 } else if (0 == strcmp(cur_arg, "--print-all")) {
756 optimizer->SetPrintAll(&std::cerr);
757 } else if (0 == strcmp(cur_arg, "--preserve-bindings")) {
758 optimizer_options->set_preserve_bindings(true);
759 } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) {
760 optimizer_options->set_preserve_spec_constants(true);
761 } else if (0 == strcmp(cur_arg, "--time-report")) {
762 optimizer->SetTimeReport(&std::cerr);
763 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
764 validator_options->SetRelaxStructStore(true);
765 } else if (0 == strncmp(cur_arg, "--max-id-bound=",
766 sizeof("--max-id-bound=") - 1)) {
767 auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
768 // Will not allow values in the range [2^31,2^32).
769 uint32_t max_id_bound =
770 static_cast<uint32_t>(atoi(split_flag.second.c_str()));
771
772 // That SPIR-V mandates the minimum value for max id bound but
773 // implementations may allow higher minimum bounds.
774 if (max_id_bound < kDefaultMaxIdBound) {
775 spvtools::Error(opt_diagnostic, nullptr, {},
776 "The max id bound must be at least 0x3FFFFF");
777 return {OPT_STOP, 1};
778 }
779 optimizer_options->set_max_id_bound(max_id_bound);
780 validator_options->SetUniversalLimit(spv_validator_limit_max_id_bound,
781 max_id_bound);
782 } else if (0 == strncmp(cur_arg,
783 "--target-env=", sizeof("--target-env=") - 1)) {
784 const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
785 const auto target_env_str = split_flag.second.c_str();
786 spv_target_env target_env;
787 if (!spvParseTargetEnv(target_env_str, &target_env)) {
788 spvtools::Error(opt_diagnostic, nullptr, {},
789 "Invalid value passed to --target-env");
790 return {OPT_STOP, 1};
791 }
792 optimizer->SetTargetEnv(target_env);
793 } else if (0 == strcmp(cur_arg, "--validate-after-all")) {
794 optimizer->SetValidateAfterAll(true);
795 } else if (0 == strcmp(cur_arg, "--before-hlsl-legalization")) {
796 validator_options->SetBeforeHlslLegalization(true);
797 } else if (0 == strcmp(cur_arg, "--relax-logical-pointer")) {
798 validator_options->SetRelaxLogicalPointer(true);
799 } else if (0 == strcmp(cur_arg, "--relax-block-layout")) {
800 validator_options->SetRelaxBlockLayout(true);
801 } else if (0 == strcmp(cur_arg, "--scalar-block-layout")) {
802 validator_options->SetScalarBlockLayout(true);
803 } else if (0 == strcmp(cur_arg, "--workgroup-scalar-block-layout")) {
804 validator_options->SetWorkgroupScalarBlockLayout(true);
805 } else if (0 == strcmp(cur_arg, "--skip-block-layout")) {
806 validator_options->SetSkipBlockLayout(true);
807 } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
808 validator_options->SetRelaxStructStore(true);
809 } else if (0 == strcmp(cur_arg, "--preserve-interface")) {
810 preserve_interface = true;
811 } else {
812 // Some passes used to accept the form '--pass arg', canonicalize them
813 // to '--pass=arg'.
814 pass_flags.push_back(CanonicalizeFlag(argv, argc, &argi));
815
816 // If we were requested to legalize SPIR-V generated from the HLSL
817 // front-end, skip validation.
818 if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
819 validator_options->SetBeforeHlslLegalization(true);
820 }
821 }
822 } else {
823 if (!*in_file) {
824 *in_file = cur_arg;
825 } else {
826 spvtools::Error(opt_diagnostic, nullptr, {},
827 "More than one input file specified");
828 return {OPT_STOP, 1};
829 }
830 }
831 }
832
833 if (!optimizer->RegisterPassesFromFlags(pass_flags, preserve_interface)) {
834 return {OPT_STOP, 1};
835 }
836
837 return {OPT_CONTINUE, 0};
838 }
839
840 } // namespace
841
main(int argc,const char ** argv)842 int main(int argc, const char** argv) {
843 const char* in_file = nullptr;
844 const char* out_file = nullptr;
845
846 spv_target_env target_env = kDefaultEnvironment;
847
848 spvtools::Optimizer optimizer(target_env);
849 optimizer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
850
851 spvtools::ValidatorOptions validator_options;
852 spvtools::OptimizerOptions optimizer_options;
853 OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
854 &validator_options, &optimizer_options);
855 optimizer_options.set_validator_options(validator_options);
856
857 if (status.action == OPT_STOP) {
858 return status.code;
859 }
860
861 if (out_file == nullptr) {
862 spvtools::Error(opt_diagnostic, nullptr, {}, "-o required");
863 return 1;
864 }
865
866 std::vector<uint32_t> binary;
867 if (!ReadBinaryFile<uint32_t>(in_file, &binary)) {
868 return 1;
869 }
870
871 // By using the same vector as input and output, we save time in the case
872 // that there was no change.
873 bool ok =
874 optimizer.Run(binary.data(), binary.size(), &binary, optimizer_options);
875
876 if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
877 return 1;
878 }
879
880 return ok ? 0 : 1;
881 }
882