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