Initial implementation of merge return pass.
[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 GetOptimizationPasses() {
52   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
53   optimizer.RegisterPerformancePasses();
54   return GetListOfPassesAsString(optimizer);
55 }
56
57 std::string GetSizePasses() {
58   spvtools::Optimizer optimizer(SPV_ENV_UNIVERSAL_1_2);
59   optimizer.RegisterSizePasses();
60   return GetListOfPassesAsString(optimizer);
61 }
62
63 void PrintUsage(const char* program) {
64   printf(
65       R"(%s - Optimize a SPIR-V binary file.
66
67 USAGE: %s [options] [<input>] -o <output>
68
69 The SPIR-V binary is read from <input>. If no file is specified,
70 or if <input> is "-", then the binary is read from standard input.
71 if <output> is "-", then the optimized output is written to
72 standard output.
73
74 NOTE: The optimizer is a work in progress.
75
76 Options:
77   --strip-debug
78                Remove all debug instructions.
79   --freeze-spec-const
80                Freeze the values of specialization constants to their default
81                values.
82   --eliminate-dead-const
83                Eliminate dead constants.
84   --fold-spec-const-op-composite
85                Fold the spec constants defined by OpSpecConstantOp or
86                OpSpecConstantComposite instructions to front-end constants
87                when possible.
88   --set-spec-const-default-value "<spec id>:<default value> ..."
89                Set the default values of the specialization constants with
90                <spec id>:<default value> pairs specified in a double-quoted
91                string. <spec id>:<default value> pairs must be separated by
92                blank spaces, and in each pair, spec id and default value must
93                be separated with colon ':' without any blank spaces in between.
94                e.g.: --set-spec-const-default-value "1:100 2:400"
95   --unify-const
96                Remove the duplicated constants.
97   --flatten-decorations
98                Replace decoration groups with repeated OpDecorate and
99                OpMemberDecorate instructions.
100   --compact-ids
101                Remap result ids to a compact range starting from %%1 and without
102                any gaps.
103   --cfg-cleanup
104                Cleanup the control flow graph. This will remove any unnecessary
105                code from the CFG like unreachable code. Performed on entry
106                point call tree functions and exported functions.
107   --inline-entry-points-exhaustive
108                Exhaustively inline all function calls in entry point call tree
109                functions. Currently does not inline calls to functions with
110                early return in a loop.
111   --convert-local-access-chains
112                Convert constant index access chain loads/stores into
113                equivalent load/stores with inserts and extracts. Performed
114                on function scope variables referenced only with load, store,
115                and constant index access chains in entry point call tree
116                functions.
117   --eliminate-common-uniform
118                Perform load/load elimination for duplicate uniform values.
119                Converts any constant index access chain uniform loads into
120                its equivalent load and extract. Some loads will be moved
121                to facilitate sharing. Performed only on entry point
122                call tree functions.
123   --eliminate-local-single-block
124                Perform single-block store/load and load/load elimination.
125                Performed only on function scope variables in entry point
126                call tree functions.
127   --eliminate-local-single-store
128                Replace stores and loads of function scope variables that are
129                only stored once. Performed on variables referenceed only with
130                loads and stores. Performed only on entry point call tree
131                functions.
132   --eliminate-local-multi-store
133                Replace stores and loads of function scope variables that are
134                stored multiple times. Performed on variables referenceed only
135                with loads and stores. Performed only on entry point call tree
136                functions.
137   --eliminate-insert-extract
138                Replace extract from a sequence of inserts with the
139                corresponding value. Performed only on entry point call tree
140                functions.
141   --eliminate-dead-code-aggressive
142                Delete instructions which do not contribute to a function's
143                output. Performed only on entry point call tree functions.
144   --eliminate-dead-branches
145                Convert conditional branches with constant condition to the
146                indicated unconditional brranch. Delete all resulting dead
147                code. Performed only on entry point call tree functions.
148   --eliminate-dead-functions
149                Deletes functions that cannot be reached from entry points or
150                exported functions.
151   --merge-blocks
152                Join two blocks into a single block if the second has the
153                first as its only predecessor. Performed only on entry point
154                call tree functions.
155   --merge-return
156                Replace all return instructions with unconditional branches to
157                a new basic block containing an unified return.
158
159                This pass does not currently support structured control flow. It
160                makes no changes if the shader capability is detected.
161   --strength-reduction
162                Replaces instructions with equivalent and less expensive ones.
163   --eliminate-dead-variables
164                Deletes module scope variables that are not referenced.
165   --relax-store-struct
166                Allow store from one struct type to a different type with
167                compatible layout and members. This option is forwarded to the
168                validator.
169   -O
170                Optimize for performance. Apply a sequence of transformations
171                in an attempt to improve the performance of the generated
172                code. For this version of the optimizer, this flag is equivalent
173                to specifying the following optimization code names:
174                %s
175   -Os
176                Optimize for size. Apply a sequence of transformations in an
177                attempt to minimize the size of the generated code. For this
178                version of the optimizer, this flag is equivalent to specifying
179                the following optimization code names:
180                %s
181
182                NOTE: The specific transformations done by -O and -Os change
183                      from release to release.
184   -Oconfig=<file>
185                Apply the sequence of transformations indicated in <file>.
186                This file contains a sequence of strings separated by whitespace
187                (tabs, newlines or blanks). Each string is one of the flags
188                accepted by spirv-opt. Optimizations will be applied in the
189                sequence they appear in the file. This is equivalent to
190                specifying all the flags on the command line. For example,
191                given the file opts.cfg with the content:
192
193                 --inline-entry-points-exhaustive
194                 --eliminate-dead-code-aggressive
195
196                The following two invocations to spirv-opt are equivalent:
197
198                $ spirv-opt -Oconfig=opts.cfg program.spv
199
200                $ spirv-opt --inline-entry-points-exhaustive \
201                     --eliminate-dead-code-aggressive program.spv
202
203                Lines starting with the character '#' in the configuration
204                file indicate a comment and will be ignored.
205
206                The -O, -Os, and -Oconfig flags act as macros. Using one of them
207                is equivalent to explicitly inserting the underlying flags at
208                that position in the command line. For example, the invocation
209                'spirv-opt --merge-blocks -O ...' applies the transformation
210                --merge-blocks followed by all the transformations implied by
211                -O.
212   -h, --help
213                Print this help.
214   --version
215                Display optimizer version information.
216 )",
217       program, program, GetOptimizationPasses().c_str(),
218       GetSizePasses().c_str());
219 }
220
221 // Reads command-line flags  the file specified in |oconfig_flag|. This string
222 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
223 // string and extracts the file name after the '=' sign.
224 //
225 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
226 //
227 // This function returns true on success, false on failure.
228 bool ReadFlagsFromFile(const char* oconfig_flag,
229                        std::vector<std::string>* file_flags) {
230   const char* fname = strchr(oconfig_flag, '=');
231   if (fname == nullptr || fname[0] != '=') {
232     fprintf(stderr, "error: Invalid -Oconfig flag %s\n", oconfig_flag);
233     return false;
234   }
235   fname++;
236
237   std::ifstream input_file;
238   input_file.open(fname);
239   if (input_file.fail()) {
240     fprintf(stderr, "error: Could not open file '%s'\n", fname);
241     return false;
242   }
243
244   while (!input_file.eof()) {
245     std::string flag;
246     input_file >> flag;
247     if (flag.length() > 0 && flag[0] != '#') {
248       file_flags->push_back(flag);
249     }
250   }
251
252   return true;
253 }
254
255 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
256                      const char** in_file, const char** out_file,
257                      spv_validator_options options);
258
259 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
260 // the spirv-opt binary (used to build a new argv vector for the recursive
261 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
262 // |optimizer|, |in_file| and |out_file| are as in ParseFlags.
263 //
264 // This returns the same OptStatus instance returned by ParseFlags.
265 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
266                            Optimizer* optimizer, const char** in_file,
267                            const char** out_file) {
268   std::vector<std::string> flags;
269   flags.push_back(prog_name);
270
271   std::vector<std::string> file_flags;
272   if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
273     fprintf(stderr,
274             "error: Could not read optimizer flags from configuration file\n");
275     return {OPT_STOP, 1};
276   }
277   flags.insert(flags.end(), file_flags.begin(), file_flags.end());
278
279   const char** new_argv = new const char*[flags.size()];
280   for (size_t i = 0; i < flags.size(); i++) {
281     if (flags[i].find("-Oconfig=") != std::string::npos) {
282       fprintf(stderr,
283               "error: Flag -Oconfig= may not be used inside the configuration "
284               "file\n");
285       return {OPT_STOP, 1};
286     }
287     new_argv[i] = flags[i].c_str();
288   }
289
290   return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
291                     in_file, out_file, nullptr);
292 }
293
294 // Parses command-line flags. |argc| contains the number of command-line flags.
295 // |argv| points to an array of strings holding the flags. |optimizer| is the
296 // Optimizer instance used to optimize the program.
297 //
298 // On return, this function stores the name of the input program in |in_file|.
299 // The name of the output file in |out_file|. The return value indicates whether
300 // optimization should continue and a status code indicating an error or
301 // success.
302 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
303                      const char** in_file, const char** out_file,
304                      spv_validator_options options) {
305   for (int argi = 1; argi < argc; ++argi) {
306     const char* cur_arg = argv[argi];
307     if ('-' == cur_arg[0]) {
308       if (0 == strcmp(cur_arg, "--version")) {
309         printf("%s\n", spvSoftwareVersionDetailsString());
310         return {OPT_STOP, 0};
311       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
312         PrintUsage(argv[0]);
313         return {OPT_STOP, 0};
314       } else if (0 == strcmp(cur_arg, "-o")) {
315         if (!*out_file && argi + 1 < argc) {
316           *out_file = argv[++argi];
317         } else {
318           PrintUsage(argv[0]);
319           return {OPT_STOP, 1};
320         }
321       } else if (0 == strcmp(cur_arg, "--strip-debug")) {
322         optimizer->RegisterPass(CreateStripDebugInfoPass());
323       } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) {
324         if (++argi < argc) {
325           auto spec_ids_vals =
326               opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
327                   argv[argi]);
328           if (!spec_ids_vals) {
329             fprintf(stderr,
330                     "error: Invalid argument for "
331                     "--set-spec-const-default-value: %s\n",
332                     argv[argi]);
333             return {OPT_STOP, 1};
334           }
335           optimizer->RegisterPass(
336               CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals)));
337         } else {
338           fprintf(
339               stderr,
340               "error: Expected a string of <spec id>:<default value> pairs.");
341           return {OPT_STOP, 1};
342         }
343       } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
344         optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
345       } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
346         optimizer->RegisterPass(CreateInlineExhaustivePass());
347       } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) {
348         optimizer->RegisterPass(CreateInlineOpaquePass());
349       } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) {
350         optimizer->RegisterPass(CreateLocalAccessChainConvertPass());
351       } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) {
352         optimizer->RegisterPass(CreateAggressiveDCEPass());
353       } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) {
354         optimizer->RegisterPass(CreateInsertExtractElimPass());
355       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) {
356         optimizer->RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
357       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) {
358         optimizer->RegisterPass(CreateLocalSingleStoreElimPass());
359       } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
360         optimizer->RegisterPass(CreateBlockMergePass());
361       } else if (0 == strcmp(cur_arg, "--merge-return")) {
362         optimizer->RegisterPass(CreateMergeReturnPass());
363       } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
364         optimizer->RegisterPass(CreateDeadBranchElimPass());
365       } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {
366         optimizer->RegisterPass(CreateEliminateDeadFunctionsPass());
367       } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
368         optimizer->RegisterPass(CreateLocalMultiStoreElimPass());
369       } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {
370         optimizer->RegisterPass(CreateCommonUniformElimPass());
371       } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
372         optimizer->RegisterPass(CreateEliminateDeadConstantPass());
373       } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) {
374         optimizer->RegisterPass(CreateDeadVariableEliminationPass());
375       } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {
376         optimizer->RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
377       } else if (0 == strcmp(cur_arg, "--strength-reduction")) {
378         optimizer->RegisterPass(CreateStrengthReductionPass());
379       } else if (0 == strcmp(cur_arg, "--unify-const")) {
380         optimizer->RegisterPass(CreateUnifyConstantPass());
381       } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
382         optimizer->RegisterPass(CreateFlattenDecorationPass());
383       } else if (0 == strcmp(cur_arg, "--compact-ids")) {
384         optimizer->RegisterPass(CreateCompactIdsPass());
385       } else if (0 == strcmp(cur_arg, "--cfg-cleanup")) {
386         optimizer->RegisterPass(CreateCFGCleanupPass());
387       } else if (0 == strcmp(cur_arg, "--relax-store-struct")) {
388         options->relax_struct_store = true;
389       } else if (0 == strcmp(cur_arg, "-O")) {
390         optimizer->RegisterPerformancePasses();
391       } else if (0 == strcmp(cur_arg, "-Os")) {
392         optimizer->RegisterSizePasses();
393       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
394         OptStatus status =
395             ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file);
396         if (status.action != OPT_CONTINUE) {
397           return status;
398         }
399       } else if ('\0' == cur_arg[1]) {
400         // Setting a filename of "-" to indicate stdin.
401         if (!*in_file) {
402           *in_file = cur_arg;
403         } else {
404           fprintf(stderr, "error: More than one input file specified\n");
405           return {OPT_STOP, 1};
406         }
407       } else {
408         fprintf(
409             stderr,
410             "error: Unknown flag '%s'. Use --help for a list of valid flags\n",
411             cur_arg);
412         return {OPT_STOP, 1};
413       }
414     } else {
415       if (!*in_file) {
416         *in_file = cur_arg;
417       } else {
418         fprintf(stderr, "error: More than one input file specified\n");
419         return {OPT_STOP, 1};
420       }
421     }
422   }
423
424   return {OPT_CONTINUE, 0};
425 }
426
427 }  // namespace
428
429 int main(int argc, const char** argv) {
430   const char* in_file = nullptr;
431   const char* out_file = nullptr;
432
433   spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
434   spv_validator_options options = spvValidatorOptionsCreate();
435
436   spvtools::Optimizer optimizer(target_env);
437   optimizer.SetMessageConsumer([](spv_message_level_t level, const char* source,
438                                   const spv_position_t& position,
439                                   const char* message) {
440     std::cerr << StringifyMessage(level, source, position, message)
441               << std::endl;
442   });
443
444   OptStatus status =
445       ParseFlags(argc, argv, &optimizer, &in_file, &out_file, options);
446
447   if (status.action == OPT_STOP) {
448     return status.code;
449   }
450
451   if (out_file == nullptr) {
452     fprintf(stderr, "error: -o required\n");
453     return 1;
454   }
455
456   std::vector<uint32_t> binary;
457   if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
458     return 1;
459   }
460
461   // Let's do validation first.
462   spv_context context = spvContextCreate(target_env);
463   spv_diagnostic diagnostic = nullptr;
464   spv_const_binary_t binary_struct = {binary.data(), binary.size()};
465   spv_result_t error =
466       spvValidateWithOptions(context, options, &binary_struct, &diagnostic);
467   if (error) {
468     spvDiagnosticPrint(diagnostic);
469     spvDiagnosticDestroy(diagnostic);
470     spvValidatorOptionsDestroy(options);
471     spvContextDestroy(context);
472     return error;
473   }
474   spvDiagnosticDestroy(diagnostic);
475   spvValidatorOptionsDestroy(options);
476   spvContextDestroy(context);
477
478   // By using the same vector as input and output, we save time in the case
479   // that there was no change.
480   bool ok = optimizer.Run(binary.data(), binary.size(), &binary);
481
482   if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
483     return 1;
484   }
485
486   return ok ? 0 : 1;
487 }