SROA: Do replacement on structs with no partial references.
[platform/upstream/SPIRV-Tools.git] / tools / opt / opt.cpp
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 <spirv_validator_options.h>
16 #include <algorithm>
17 #include <cassert>
18 #include <cstring>
19 #include <fstream>
20 #include <iostream>
21 #include <memory>
22 #include <sstream>
23 #include <vector>
24
25 #include "opt/set_spec_constant_default_value_pass.h"
26 #include "spirv-tools/optimizer.hpp"
27
28 #include "message.h"
29 #include "tools/io.h"
30
31 using namespace spvtools;
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 std::string GetListOfPassesAsString(const spvtools::Optimizer& optimizer) {
44   std::stringstream ss;
45   for (const auto& name : optimizer.GetPassNames()) {
46     ss << "\n\t\t" << name;
47   }
48   return ss.str();
49 }
50
51 std::string GetLegalizationPasses() {
52   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
53   optimizer.RegisterLegalizationPasses();
54   return GetListOfPassesAsString(optimizer);
55 }
56
57 std::string GetOptimizationPasses() {
58   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
59   optimizer.RegisterPerformancePasses();
60   return GetListOfPassesAsString(optimizer);
61 }
62
63 std::string GetSizePasses() {
64   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
65   optimizer.RegisterSizePasses();
66   return GetListOfPassesAsString(optimizer);
67 }
68
69 void PrintUsage(const char* program) {
70   // NOTE: Please maintain flags in lexicographical order.
71   printf(
72       R"(%s - Optimize a SPIR-V binary file.
73
74 USAGE: %s [options] [<input>] -o <output>
75
76 The SPIR-V binary is read from <input>. If no file is specified,
77 or if <input> is "-", then the binary is read from standard input.
78 if <output> is "-", then the optimized output is written to
79 standard output.
80
81 NOTE: The optimizer is a work in progress.
82
83 Options (in lexicographical order):
84   --ccp
85                Apply the conditional constant propagation transform.  This will
86                propagate constant values throughout the program, and simplify
87                expressions and conditional jumps with known predicate
88                values.  Performed on entry point call tree functions and
89                exported functions.
90   --cfg-cleanup
91                Cleanup the control flow graph. This will remove any unnecessary
92                code from the CFG like unreachable code. Performed on entry
93                point call tree functions and exported functions.
94   --compact-ids
95                Remap result ids to a compact range starting from %%1 and without
96                any gaps.
97   --convert-local-access-chains
98                Convert constant index access chain loads/stores into
99                equivalent load/stores with inserts and extracts. Performed
100                on function scope variables referenced only with load, store,
101                and constant index access chains in entry point call tree
102                functions.
103   --eliminate-common-uniform
104                Perform load/load elimination for duplicate uniform values.
105                Converts any constant index access chain uniform loads into
106                its equivalent load and extract. Some loads will be moved
107                to facilitate sharing. Performed only on entry point
108                call tree functions.
109   --eliminate-dead-branches
110                Convert conditional branches with constant condition to the
111                indicated unconditional brranch. Delete all resulting dead
112                code. Performed only on entry point call tree functions.
113   --eliminate-dead-code-aggressive
114                Delete instructions which do not contribute to a function's
115                output. Performed only on entry point call tree functions.
116   --eliminate-dead-const
117                Eliminate dead constants.
118   --eliminate-dead-functions
119                Deletes functions that cannot be reached from entry points or
120                exported functions.
121   --eliminate-dead-insert
122                Deletes unreferenced inserts into composites, most notably
123                unused stores to vector components, that are not removed by
124                aggressive dead code elimination.
125   --eliminate-dead-variables
126                Deletes module scope variables that are not referenced.
127   --eliminate-insert-extract
128                Replace extract from a sequence of inserts with the
129                corresponding value. Performed only on entry point call tree
130                functions.
131   --eliminate-local-multi-store
132                Replace stores and loads of function scope variables that are
133                stored multiple times. Performed on variables referenceed only
134                with loads and stores. Performed only on entry point call tree
135                functions.
136   --eliminate-local-single-block
137                Perform single-block store/load and load/load elimination.
138                Performed only on function scope variables in entry point
139                call tree functions.
140   --eliminate-local-single-store
141                Replace stores and loads of function scope variables that are
142                only stored once. Performed on variables referenceed only with
143                loads and stores. Performed only on entry point call tree
144                functions.
145   --flatten-decorations
146                Replace decoration groups with repeated OpDecorate and
147                OpMemberDecorate instructions.
148   --fold-spec-const-op-composite
149                Fold the spec constants defined by OpSpecConstantOp or
150                OpSpecConstantComposite instructions to front-end constants
151                when possible.
152   --freeze-spec-const
153                Freeze the values of specialization constants to their default
154                values.
155   --if-conversion
156                Convert if-then-else like assignments into OpSelect.
157   --inline-entry-points-exhaustive
158                Exhaustively inline all function calls in entry point call tree
159                functions. Currently does not inline calls to functions with
160                early return in a loop.
161   --legalize-hlsl
162                Runs a series of optimizations that attempts to take SPIR-V
163                generated by and HLSL front-end and generate legal Vulkan SPIR-V.
164                The optimizations are:
165                %s
166
167                Note this does not guarantee legal code. This option implies
168                --skip-validation.
169   --local-redundancy-elimination
170                Looks for instructions in the same basic block that compute the
171                same value, and deletes the redundant ones.
172   --merge-blocks
173                Join two blocks into a single block if the second has the
174                first as its only predecessor. Performed only on entry point
175                call tree functions.
176   --merge-return
177                Replace all return instructions with unconditional branches to
178                a new basic block containing an unified return.
179                This pass does not currently support structured control flow. It
180                makes no changes if the shader capability is detected.
181   --local-redundancy-elimination
182                Looks for instructions in the same basic block that compute the
183                same value, and deletes the redundant ones.
184   -O
185                Optimize for performance. Apply a sequence of transformations
186                in an attempt to improve the performance of the generated
187                code. For this version of the optimizer, this flag is equivalent
188                to specifying the following optimization code names:
189                %s
190   -Os
191                Optimize for size. Apply a sequence of transformations in an
192                attempt to minimize the size of the generated code. For this
193                version of the optimizer, this flag is equivalent to specifying
194                the following optimization code names:
195                %s
196
197                NOTE: The specific transformations done by -O and -Os change
198                      from release to release.
199   -Oconfig=<file>
200                Apply the sequence of transformations indicated in <file>.
201                This file contains a sequence of strings separated by whitespace
202                (tabs, newlines or blanks). Each string is one of the flags
203                accepted by spirv-opt. Optimizations will be applied in the
204                sequence they appear in the file. This is equivalent to
205                specifying all the flags on the command line. For example,
206                given the file opts.cfg with the content:
207
208                 --inline-entry-points-exhaustive
209                 --eliminate-dead-code-aggressive
210
211                The following two invocations to spirv-opt are equivalent:
212
213                $ spirv-opt -Oconfig=opts.cfg program.spv
214
215                $ spirv-opt --inline-entry-points-exhaustive \
216                     --eliminate-dead-code-aggressive program.spv
217
218                Lines starting with the character '#' in the configuration
219                file indicate a comment and will be ignored.
220
221                The -O, -Os, and -Oconfig flags act as macros. Using one of them
222                is equivalent to explicitly inserting the underlying flags at
223                that position in the command line. For example, the invocation
224                'spirv-opt --merge-blocks -O ...' applies the transformation
225                --merge-blocks followed by all the transformations implied by
226                -O.
227   --print-all
228                Print SPIR-V assembly to standard error output before each pass
229                and after the last pass.
230   --private-to-local
231                Change the scope of private variables that are used in a single
232                function to that function.
233   --remove-duplicates
234                Removes duplicate types, decorations, capabilities and extension
235                instructions.
236   --redundancy-elimination
237                Looks for instructions in the same function that compute the
238                same value, and deletes the redundant ones.
239   --relax-struct-store
240                Allow store from one struct type to a different type with
241                compatible layout and members. This option is forwarded to the
242                validator.
243   --replace-invalid-opcode
244                Replaces instructions whose opcode is valid for shader modules,
245                but not for the current shader stage.  To have an effect, all
246                entry points must have the same execution model.
247   --scalar-replacement
248                Replace aggregate function scope variables that are only accessed
249                via their elements with new function variables representing each
250                element.
251   --set-spec-const-default-value "<spec id>:<default value> ..."
252                Set the default values of the specialization constants with
253                <spec id>:<default value> pairs specified in a double-quoted
254                string. <spec id>:<default value> pairs must be separated by
255                blank spaces, and in each pair, spec id and default value must
256                be separated with colon ':' without any blank spaces in between.
257                e.g.: --set-spec-const-default-value "1:100 2:400"
258   --simplify-instructions
259                Will simplfy all instructions in the function as much as
260                possible.
261   --skip-validation
262                Will not validate the SPIR-V before optimizing.  If the SPIR-V
263                is invalid, the optimizer may fail or generate incorrect code.
264                This options should be used rarely, and with caution.
265   --strength-reduction
266                Replaces instructions with equivalent and less expensive ones.
267   --strip-debug
268                Remove all debug instructions.
269   --workaround-1209
270                Rewrites instructions for which there are known driver bugs to
271                avoid triggering those bugs.
272                Current workarounds: Avoid OpUnreachable in loops.
273   --unify-const
274                Remove the duplicated constants.
275   -h, --help
276                Print this help.
277   --version
278                Display optimizer version information.
279 )",
280       program, program, GetLegalizationPasses().c_str(),
281       GetOptimizationPasses().c_str(), GetSizePasses().c_str());
282 }
283
284 // Reads command-line flags  the file specified in |oconfig_flag|. This string
285 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
286 // string and extracts the file name after the '=' sign.
287 //
288 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
289 //
290 // This function returns true on success, false on failure.
291 bool ReadFlagsFromFile(const char* oconfig_flag,
292                        std::vector<std::string>* file_flags) {
293   const char* fname = strchr(oconfig_flag, '=');
294   if (fname == nullptr || fname[0] != '=') {
295     fprintf(stderr, "error: Invalid -Oconfig flag %s\n", oconfig_flag);
296     return false;
297   }
298   fname++;
299
300   std::ifstream input_file;
301   input_file.open(fname);
302   if (input_file.fail()) {
303     fprintf(stderr, "error: Could not open file '%s'\n", fname);
304     return false;
305   }
306
307   while (!input_file.eof()) {
308     std::string flag;
309     input_file >> flag;
310     if (flag.length() > 0 && flag[0] != '#') {
311       file_flags->push_back(flag);
312     }
313   }
314
315   return true;
316 }
317
318 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
319                      const char** in_file, const char** out_file,
320                      spv_validator_options options, bool* skip_validator);
321
322 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
323 // the spirv-opt binary (used to build a new argv vector for the recursive
324 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
325 // |optimizer|, |in_file| and |out_file| are as in ParseFlags.
326 //
327 // This returns the same OptStatus instance returned by ParseFlags.
328 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
329                            Optimizer* optimizer, const char** in_file,
330                            const char** out_file) {
331   std::vector<std::string> flags;
332   flags.push_back(prog_name);
333
334   std::vector<std::string> file_flags;
335   if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
336     fprintf(stderr,
337             "error: Could not read optimizer flags from configuration file\n");
338     return {OPT_STOP, 1};
339   }
340   flags.insert(flags.end(), file_flags.begin(), file_flags.end());
341
342   const char** new_argv = new const char*[flags.size()];
343   for (size_t i = 0; i < flags.size(); i++) {
344     if (flags[i].find("-Oconfig=") != std::string::npos) {
345       fprintf(stderr,
346               "error: Flag -Oconfig= may not be used inside the configuration "
347               "file\n");
348       return {OPT_STOP, 1};
349     }
350     new_argv[i] = flags[i].c_str();
351   }
352
353   bool skip_validator = false;
354   return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
355                     in_file, out_file, nullptr, &skip_validator);
356 }
357
358 // Parses command-line flags. |argc| contains the number of command-line flags.
359 // |argv| points to an array of strings holding the flags. |optimizer| is the
360 // Optimizer instance used to optimize the program.
361 //
362 // On return, this function stores the name of the input program in |in_file|.
363 // The name of the output file in |out_file|. The return value indicates whether
364 // optimization should continue and a status code indicating an error or
365 // success.
366 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
367                      const char** in_file, const char** out_file,
368                      spv_validator_options options, bool* skip_validator) {
369   for (int argi = 1; argi < argc; ++argi) {
370     const char* cur_arg = argv[argi];
371     if ('-' == cur_arg[0]) {
372       if (0 == strcmp(cur_arg, "--version")) {
373         printf("%s\n", spvSoftwareVersionDetailsString());
374         return {OPT_STOP, 0};
375       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
376         PrintUsage(argv[0]);
377         return {OPT_STOP, 0};
378       } else if (0 == strcmp(cur_arg, "-o")) {
379         if (!*out_file && argi + 1 < argc) {
380           *out_file = argv[++argi];
381         } else {
382           PrintUsage(argv[0]);
383           return {OPT_STOP, 1};
384         }
385       } else if (0 == strcmp(cur_arg, "--strip-debug")) {
386         optimizer->RegisterPass(CreateStripDebugInfoPass());
387       } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) {
388         if (++argi < argc) {
389           auto spec_ids_vals =
390               opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
391                   argv[argi]);
392           if (!spec_ids_vals) {
393             fprintf(stderr,
394                     "error: Invalid argument for "
395                     "--set-spec-const-default-value: %s\n",
396                     argv[argi]);
397             return {OPT_STOP, 1};
398           }
399           optimizer->RegisterPass(
400               CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals)));
401         } else {
402           fprintf(
403               stderr,
404               "error: Expected a string of <spec id>:<default value> pairs.");
405           return {OPT_STOP, 1};
406         }
407       } else if (0 == strcmp(cur_arg, "--if-conversion")) {
408         optimizer->RegisterPass(CreateIfConversionPass());
409       } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
410         optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
411       } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
412         optimizer->RegisterPass(CreateInlineExhaustivePass());
413       } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) {
414         optimizer->RegisterPass(CreateInlineOpaquePass());
415       } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) {
416         optimizer->RegisterPass(CreateLocalAccessChainConvertPass());
417       } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) {
418         optimizer->RegisterPass(CreateAggressiveDCEPass());
419       } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) {
420         optimizer->RegisterPass(CreateInsertExtractElimPass());
421       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) {
422         optimizer->RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
423       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) {
424         optimizer->RegisterPass(CreateLocalSingleStoreElimPass());
425       } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
426         optimizer->RegisterPass(CreateBlockMergePass());
427       } else if (0 == strcmp(cur_arg, "--merge-return")) {
428         optimizer->RegisterPass(CreateMergeReturnPass());
429       } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
430         optimizer->RegisterPass(CreateDeadBranchElimPass());
431       } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {
432         optimizer->RegisterPass(CreateEliminateDeadFunctionsPass());
433       } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
434         optimizer->RegisterPass(CreateLocalMultiStoreElimPass());
435       } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {
436         optimizer->RegisterPass(CreateCommonUniformElimPass());
437       } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
438         optimizer->RegisterPass(CreateEliminateDeadConstantPass());
439       } else if (0 == strcmp(cur_arg, "--eliminate-dead-inserts")) {
440         optimizer->RegisterPass(CreateDeadInsertElimPass());
441       } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) {
442         optimizer->RegisterPass(CreateDeadVariableEliminationPass());
443       } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {
444         optimizer->RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
445       } else if (0 == strcmp(cur_arg, "--scalar-replacement")) {
446         optimizer->RegisterPass(CreateScalarReplacementPass());
447       } else if (0 == strcmp(cur_arg, "--strength-reduction")) {
448         optimizer->RegisterPass(CreateStrengthReductionPass());
449       } else if (0 == strcmp(cur_arg, "--unify-const")) {
450         optimizer->RegisterPass(CreateUnifyConstantPass());
451       } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
452         optimizer->RegisterPass(CreateFlattenDecorationPass());
453       } else if (0 == strcmp(cur_arg, "--compact-ids")) {
454         optimizer->RegisterPass(CreateCompactIdsPass());
455       } else if (0 == strcmp(cur_arg, "--cfg-cleanup")) {
456         optimizer->RegisterPass(CreateCFGCleanupPass());
457       } else if (0 == strcmp(cur_arg, "--local-redundancy-elimination")) {
458         optimizer->RegisterPass(CreateLocalRedundancyEliminationPass());
459       } else if (0 == strcmp(cur_arg, "--redundancy-elimination")) {
460         optimizer->RegisterPass(CreateRedundancyEliminationPass());
461       } else if (0 == strcmp(cur_arg, "--private-to-local")) {
462         optimizer->RegisterPass(CreatePrivateToLocalPass());
463       } else if (0 == strcmp(cur_arg, "--remove-duplicates")) {
464         optimizer->RegisterPass(CreateRemoveDuplicatesPass());
465       } else if (0 == strcmp(cur_arg, "--workaround-1209")) {
466         optimizer->RegisterPass(CreateWorkaround1209Pass());
467       } else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
468         options->relax_struct_store = true;
469       } else if (0 == strcmp(cur_arg, "--replace-invalid-opcode")) {
470         optimizer->RegisterPass(CreateReplaceInvalidOpcodePass());
471       } else if (0 == strcmp(cur_arg, "--simplify-instructions")) {
472         optimizer->RegisterPass(CreateSimplificationPass());
473       } else if (0 == strcmp(cur_arg, "--skip-validation")) {
474         *skip_validator = true;
475       } else if (0 == strcmp(cur_arg, "-O")) {
476         optimizer->RegisterPerformancePasses();
477       } else if (0 == strcmp(cur_arg, "-Os")) {
478         optimizer->RegisterSizePasses();
479       } else if (0 == strcmp(cur_arg, "--legalize-hlsl")) {
480         *skip_validator = true;
481         optimizer->RegisterLegalizationPasses();
482       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
483         OptStatus status =
484             ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file);
485         if (status.action != OPT_CONTINUE) {
486           return status;
487         }
488       } else if (0 == strcmp(cur_arg, "--ccp")) {
489         optimizer->RegisterPass(CreateCCPPass());
490       } else if (0 == strcmp(cur_arg, "--print-all")) {
491         optimizer->SetPrintAll(&std::cerr);
492       } else if ('\0' == cur_arg[1]) {
493         // Setting a filename of "-" to indicate stdin.
494         if (!*in_file) {
495           *in_file = cur_arg;
496         } else {
497           fprintf(stderr, "error: More than one input file specified\n");
498           return {OPT_STOP, 1};
499         }
500       } else {
501         fprintf(
502             stderr,
503             "error: Unknown flag '%s'. Use --help for a list of valid flags\n",
504             cur_arg);
505         return {OPT_STOP, 1};
506       }
507     } else {
508       if (!*in_file) {
509         *in_file = cur_arg;
510       } else {
511         fprintf(stderr, "error: More than one input file specified\n");
512         return {OPT_STOP, 1};
513       }
514     }
515   }
516
517   return {OPT_CONTINUE, 0};
518 }
519
520 }  // namespace
521
522 int main(int argc, const char** argv) {
523   const char* in_file = nullptr;
524   const char* out_file = nullptr;
525   bool skip_validator = false;
526
527   spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
528   spv_validator_options options = spvValidatorOptionsCreate();
529
530   spvtools::Optimizer optimizer(target_env);
531   optimizer.SetMessageConsumer([](spv_message_level_t level, const char* source,
532                                   const spv_position_t& position,
533                                   const char* message) {
534     std::cerr << StringifyMessage(level, source, position, message)
535               << std::endl;
536   });
537
538   OptStatus status = ParseFlags(argc, argv, &optimizer, &in_file, &out_file,
539                                 options, &skip_validator);
540
541   if (status.action == OPT_STOP) {
542     return status.code;
543   }
544
545   if (out_file == nullptr) {
546     fprintf(stderr, "error: -o required\n");
547     return 1;
548   }
549
550   std::vector<uint32_t> binary;
551   if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
552     return 1;
553   }
554
555   if (!skip_validator) {
556     // Let's do validation first.
557     spv_context context = spvContextCreate(target_env);
558     spv_diagnostic diagnostic = nullptr;
559     spv_const_binary_t binary_struct = {binary.data(), binary.size()};
560     spv_result_t error =
561         spvValidateWithOptions(context, options, &binary_struct, &diagnostic);
562     if (error) {
563       spvDiagnosticPrint(diagnostic);
564       spvDiagnosticDestroy(diagnostic);
565       spvValidatorOptionsDestroy(options);
566       spvContextDestroy(context);
567       return error;
568     }
569     spvDiagnosticDestroy(diagnostic);
570     spvValidatorOptionsDestroy(options);
571     spvContextDestroy(context);
572   }
573
574   // By using the same vector as input and output, we save time in the case
575   // that there was no change.
576   bool ok = optimizer.Run(binary.data(), binary.size(), &binary);
577
578   if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
579     return 1;
580   }
581
582   return ok ? 0 : 1;
583 }