4236c935816b1a330e6ead48414e0db9d9b8c259
[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   --strength-reduction
156                Replaces instructions with equivalent and less expensive ones.
157   --eliminate-dead-variables
158                Deletes module scope variables that are not referenced.
159   --relax-store-struct
160                Allow store from one struct type to a different type with
161                compatible layout and members. This option is forwarded to the
162                validator.
163   -O
164                Optimize for performance. Apply a sequence of transformations
165                in an attempt to improve the performance of the generated
166                code. For this version of the optimizer, this flag is equivalent
167                to specifying the following optimization code names:
168                %s
169   -Os
170                Optimize for size. Apply a sequence of transformations in an
171                attempt to minimize the size of the generated code. For this
172                version of the optimizer, this flag is equivalent to specifying
173                the following optimization code names:
174                %s
175
176                NOTE: The specific transformations done by -O and -Os change
177                      from release to release.
178   -Oconfig=<file>
179                Apply the sequence of transformations indicated in <file>.
180                This file contains a sequence of strings separated by whitespace
181                (tabs, newlines or blanks). Each string is one of the flags
182                accepted by spirv-opt. Optimizations will be applied in the
183                sequence they appear in the file. This is equivalent to
184                specifying all the flags on the command line. For example,
185                given the file opts.cfg with the content:
186
187                 --inline-entry-points-exhaustive
188                 --eliminate-dead-code-aggressive
189
190                The following two invocations to spirv-opt are equivalent:
191
192                $ spirv-opt -Oconfig=opts.cfg program.spv
193
194                $ spirv-opt --inline-entry-points-exhaustive \
195                     --eliminate-dead-code-aggressive program.spv
196
197                Lines starting with the character '#' in the configuration
198                file indicate a comment and will be ignored.
199
200                The -O, -Os, and -Oconfig flags act as macros. Using one of them
201                is equivalent to explicitly inserting the underlying flags at
202                that position in the command line. For example, the invocation
203                'spirv-opt --merge-blocks -O ...' applies the transformation
204                --merge-blocks followed by all the transformations implied by
205                -O.
206   -h, --help
207                Print this help.
208   --version
209                Display optimizer version information.
210 )",
211       program, program, GetOptimizationPasses().c_str(),
212       GetSizePasses().c_str());
213 }
214
215 // Reads command-line flags  the file specified in |oconfig_flag|. This string
216 // is assumed to have the form "-Oconfig=FILENAME". This function parses the
217 // string and extracts the file name after the '=' sign.
218 //
219 // Flags found in |FILENAME| are pushed at the end of the vector |file_flags|.
220 //
221 // This function returns true on success, false on failure.
222 bool ReadFlagsFromFile(const char* oconfig_flag,
223                        std::vector<std::string>* file_flags) {
224   const char* fname = strchr(oconfig_flag, '=');
225   if (fname == nullptr || fname[0] != '=') {
226     fprintf(stderr, "error: Invalid -Oconfig flag %s\n", oconfig_flag);
227     return false;
228   }
229   fname++;
230
231   std::ifstream input_file;
232   input_file.open(fname);
233   if (input_file.fail()) {
234     fprintf(stderr, "error: Could not open file '%s'\n", fname);
235     return false;
236   }
237
238   while (!input_file.eof()) {
239     std::string flag;
240     input_file >> flag;
241     if (flag.length() > 0 && flag[0] != '#') {
242       file_flags->push_back(flag);
243     }
244   }
245
246   return true;
247 }
248
249 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
250                      const char** in_file, const char** out_file,
251                      spv_validator_options options);
252
253 // Parses and handles the -Oconfig flag. |prog_name| contains the name of
254 // the spirv-opt binary (used to build a new argv vector for the recursive
255 // invocation to ParseFlags). |opt_flag| contains the -Oconfig=FILENAME flag.
256 // |optimizer|, |in_file| and |out_file| are as in ParseFlags.
257 //
258 // This returns the same OptStatus instance returned by ParseFlags.
259 OptStatus ParseOconfigFlag(const char* prog_name, const char* opt_flag,
260                            Optimizer* optimizer, const char** in_file,
261                            const char** out_file) {
262   std::vector<std::string> flags;
263   flags.push_back(prog_name);
264
265   std::vector<std::string> file_flags;
266   if (!ReadFlagsFromFile(opt_flag, &file_flags)) {
267     fprintf(stderr,
268             "error: Could not read optimizer flags from configuration file\n");
269     return {OPT_STOP, 1};
270   }
271   flags.insert(flags.end(), file_flags.begin(), file_flags.end());
272
273   const char** new_argv = new const char*[flags.size()];
274   for (size_t i = 0; i < flags.size(); i++) {
275     if (flags[i].find("-Oconfig=") != std::string::npos) {
276       fprintf(stderr,
277               "error: Flag -Oconfig= may not be used inside the configuration "
278               "file\n");
279       return {OPT_STOP, 1};
280     }
281     new_argv[i] = flags[i].c_str();
282   }
283
284   return ParseFlags(static_cast<int>(flags.size()), new_argv, optimizer,
285                     in_file, out_file, nullptr);
286 }
287
288 // Parses command-line flags. |argc| contains the number of command-line flags.
289 // |argv| points to an array of strings holding the flags. |optimizer| is the
290 // Optimizer instance used to optimize the program.
291 //
292 // On return, this function stores the name of the input program in |in_file|.
293 // The name of the output file in |out_file|. The return value indicates whether
294 // optimization should continue and a status code indicating an error or
295 // success.
296 OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
297                      const char** in_file, const char** out_file,
298                      spv_validator_options options) {
299   for (int argi = 1; argi < argc; ++argi) {
300     const char* cur_arg = argv[argi];
301     if ('-' == cur_arg[0]) {
302       if (0 == strcmp(cur_arg, "--version")) {
303         printf("%s\n", spvSoftwareVersionDetailsString());
304         return {OPT_STOP, 0};
305       } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
306         PrintUsage(argv[0]);
307         return {OPT_STOP, 0};
308       } else if (0 == strcmp(cur_arg, "-o")) {
309         if (!*out_file && argi + 1 < argc) {
310           *out_file = argv[++argi];
311         } else {
312           PrintUsage(argv[0]);
313           return {OPT_STOP, 1};
314         }
315       } else if (0 == strcmp(cur_arg, "--strip-debug")) {
316         optimizer->RegisterPass(CreateStripDebugInfoPass());
317       } else if (0 == strcmp(cur_arg, "--set-spec-const-default-value")) {
318         if (++argi < argc) {
319           auto spec_ids_vals =
320               opt::SetSpecConstantDefaultValuePass::ParseDefaultValuesString(
321                   argv[argi]);
322           if (!spec_ids_vals) {
323             fprintf(stderr,
324                     "error: Invalid argument for "
325                     "--set-spec-const-default-value: %s\n",
326                     argv[argi]);
327             return {OPT_STOP, 1};
328           }
329           optimizer->RegisterPass(
330               CreateSetSpecConstantDefaultValuePass(std::move(*spec_ids_vals)));
331         } else {
332           fprintf(
333               stderr,
334               "error: Expected a string of <spec id>:<default value> pairs.");
335           return {OPT_STOP, 1};
336         }
337       } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
338         optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
339       } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
340         optimizer->RegisterPass(CreateInlineExhaustivePass());
341       } else if (0 == strcmp(cur_arg, "--inline-entry-points-opaque")) {
342         optimizer->RegisterPass(CreateInlineOpaquePass());
343       } else if (0 == strcmp(cur_arg, "--convert-local-access-chains")) {
344         optimizer->RegisterPass(CreateLocalAccessChainConvertPass());
345       } else if (0 == strcmp(cur_arg, "--eliminate-dead-code-aggressive")) {
346         optimizer->RegisterPass(CreateAggressiveDCEPass());
347       } else if (0 == strcmp(cur_arg, "--eliminate-insert-extract")) {
348         optimizer->RegisterPass(CreateInsertExtractElimPass());
349       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-block")) {
350         optimizer->RegisterPass(CreateLocalSingleBlockLoadStoreElimPass());
351       } else if (0 == strcmp(cur_arg, "--eliminate-local-single-store")) {
352         optimizer->RegisterPass(CreateLocalSingleStoreElimPass());
353       } else if (0 == strcmp(cur_arg, "--merge-blocks")) {
354         optimizer->RegisterPass(CreateBlockMergePass());
355       } else if (0 == strcmp(cur_arg, "--eliminate-dead-branches")) {
356         optimizer->RegisterPass(CreateDeadBranchElimPass());
357       } else if (0 == strcmp(cur_arg, "--eliminate-dead-functions")) {
358         optimizer->RegisterPass(CreateEliminateDeadFunctionsPass());
359       } else if (0 == strcmp(cur_arg, "--eliminate-local-multi-store")) {
360         optimizer->RegisterPass(CreateLocalMultiStoreElimPass());
361       } else if (0 == strcmp(cur_arg, "--eliminate-common-uniform")) {
362         optimizer->RegisterPass(CreateCommonUniformElimPass());
363       } else if (0 == strcmp(cur_arg, "--eliminate-dead-const")) {
364         optimizer->RegisterPass(CreateEliminateDeadConstantPass());
365       } else if (0 == strcmp(cur_arg, "--eliminate-dead-variables")) {
366         optimizer->RegisterPass(CreateDeadVariableEliminationPass());
367       } else if (0 == strcmp(cur_arg, "--fold-spec-const-op-composite")) {
368         optimizer->RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
369       } else if (0 == strcmp(cur_arg, "--strength-reduction")) {
370         optimizer->RegisterPass(CreateStrengthReductionPass());
371       } else if (0 == strcmp(cur_arg, "--unify-const")) {
372         optimizer->RegisterPass(CreateUnifyConstantPass());
373       } else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
374         optimizer->RegisterPass(CreateFlattenDecorationPass());
375       } else if (0 == strcmp(cur_arg, "--compact-ids")) {
376         optimizer->RegisterPass(CreateCompactIdsPass());
377       } else if (0 == strcmp(cur_arg, "--cfg-cleanup")) {
378         optimizer->RegisterPass(CreateCFGCleanupPass());
379       } else if (0 == strcmp(cur_arg, "--relax-store-struct")) {
380         options->relax_struct_store = true;
381       } else if (0 == strcmp(cur_arg, "-O")) {
382         optimizer->RegisterPerformancePasses();
383       } else if (0 == strcmp(cur_arg, "-Os")) {
384         optimizer->RegisterSizePasses();
385       } else if (0 == strncmp(cur_arg, "-Oconfig=", sizeof("-Oconfig=") - 1)) {
386         OptStatus status =
387             ParseOconfigFlag(argv[0], cur_arg, optimizer, in_file, out_file);
388         if (status.action != OPT_CONTINUE) {
389           return status;
390         }
391       } else if ('\0' == cur_arg[1]) {
392         // Setting a filename of "-" to indicate stdin.
393         if (!*in_file) {
394           *in_file = cur_arg;
395         } else {
396           fprintf(stderr, "error: More than one input file specified\n");
397           return {OPT_STOP, 1};
398         }
399       } else {
400         fprintf(
401             stderr,
402             "error: Unknown flag '%s'. Use --help for a list of valid flags\n",
403             cur_arg);
404         return {OPT_STOP, 1};
405       }
406     } else {
407       if (!*in_file) {
408         *in_file = cur_arg;
409       } else {
410         fprintf(stderr, "error: More than one input file specified\n");
411         return {OPT_STOP, 1};
412       }
413     }
414   }
415
416   return {OPT_CONTINUE, 0};
417 }
418
419 }  // namespace
420
421 int main(int argc, const char** argv) {
422   const char* in_file = nullptr;
423   const char* out_file = nullptr;
424
425   spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
426   spv_validator_options options = spvValidatorOptionsCreate();
427
428   spvtools::Optimizer optimizer(target_env);
429   optimizer.SetMessageConsumer([](spv_message_level_t level, const char* source,
430                                   const spv_position_t& position,
431                                   const char* message) {
432     std::cerr << StringifyMessage(level, source, position, message)
433               << std::endl;
434   });
435
436   OptStatus status =
437       ParseFlags(argc, argv, &optimizer, &in_file, &out_file, options);
438
439   if (status.action == OPT_STOP) {
440     return status.code;
441   }
442
443   if (out_file == nullptr) {
444     fprintf(stderr, "error: -o required\n");
445     return 1;
446   }
447
448   std::vector<uint32_t> binary;
449   if (!ReadFile<uint32_t>(in_file, "rb", &binary)) {
450     return 1;
451   }
452
453   // Let's do validation first.
454   spv_context context = spvContextCreate(target_env);
455   spv_diagnostic diagnostic = nullptr;
456   spv_const_binary_t binary_struct = {binary.data(), binary.size()};
457   spv_result_t error =
458       spvValidateWithOptions(context, options, &binary_struct, &diagnostic);
459   if (error) {
460     spvDiagnosticPrint(diagnostic);
461     spvDiagnosticDestroy(diagnostic);
462     spvValidatorOptionsDestroy(options);
463     spvContextDestroy(context);
464     return error;
465   }
466   spvDiagnosticDestroy(diagnostic);
467   spvValidatorOptionsDestroy(options);
468   spvContextDestroy(context);
469
470   // By using the same vector as input and output, we save time in the case
471   // that there was no change.
472   bool ok = optimizer.Run(binary.data(), binary.size(), &binary);
473
474   if (!WriteFile<uint32_t>(out_file, "wb", binary.data(), binary.size())) {
475     return 1;
476   }
477
478   return ok ? 0 : 1;
479 }