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