From 331b223fb6f8636202265a2bfc27b42e8d424f12 Mon Sep 17 00:00:00 2001 From: Ryan Harrison Date: Fri, 5 Apr 2019 15:12:26 -0400 Subject: [PATCH] Add WebGPU->Vulkan and Vulkan->WebGPU flags in spirv-opt (#2496) Renames the existing flag '--webgpu-mode' to '--vulkan-to-webgpu' for the Vulkan->WebGPU operation, and adds a new flag '--webgpu-to-vulkan' for the WebGPU->Vulkan operation. Currently '--webgpu-to-vulkan' doesn't have any passes associated with it yet, but further patches will implement them. Fixes #2495 --- include/spirv-tools/optimizer.hpp | 13 +- source/opt/optimizer.cpp | 4 +- test/opt/optimizer_test.cpp | 17 +- tools/opt/opt.cpp | 283 +++++++++++++++++++++--------- 4 files changed, 221 insertions(+), 96 deletions(-) diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 846ad702..98661cb3 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -101,10 +101,15 @@ class Optimizer { // from time to time. Optimizer& RegisterSizePasses(); - // Registers passes that have been prescribed for WebGPU environments. - // This sequence of passes is subject to constant review and will change - // from time to time. - Optimizer& RegisterWebGPUPasses(); + // Registers passes that have been prescribed for converting from Vulkan to + // WebGPU. This sequence of passes is subject to constant review and will + // change from time to time. + Optimizer& RegisterVulkanToWebGPUPasses(); + + // Registers passes that have been prescribed for converting from WebGPU to + // Vulkan. This sequence of passes is subject to constant review and will + // change from time to time. + Optimizer& RegisterWebGPUToVulkanPasses(); // Registers passes that attempt to legalize the generated code. // diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index c17c05f3..471bb860 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -220,7 +220,7 @@ Optimizer& Optimizer::RegisterSizePasses() { .RegisterPass(CreateAggressiveDCEPass()); } -Optimizer& Optimizer::RegisterWebGPUPasses() { +Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() { return RegisterPass(CreateStripDebugInfoPass()) .RegisterPass(CreateStripAtomicCounterMemoryPass()) .RegisterPass(CreateGenerateWebGPUInitializersPass()) @@ -230,6 +230,8 @@ Optimizer& Optimizer::RegisterWebGPUPasses() { .RegisterPass(CreateDeadBranchElimPass()); } +Optimizer& Optimizer::RegisterWebGPUToVulkanPasses() { return *this; } + bool Optimizer::RegisterPassesFromFlags(const std::vector& flags) { for (const auto& flag : flags) { if (!RegisterPassFromFlag(flag)) { diff --git a/test/opt/optimizer_test.cpp b/test/opt/optimizer_test.cpp index 513aa16b..1d3e3109 100644 --- a/test/opt/optimizer_test.cpp +++ b/test/opt/optimizer_test.cpp @@ -222,9 +222,9 @@ TEST(Optimizer, CanRegisterPassesFromFlags) { EXPECT_EQ(msg_level, SPV_MSG_ERROR); } -TEST(Optimizer, WebGPUModeSetsCorrectPasses) { +TEST(Optimizer, VulkanToWebGPUModeSetsCorrectPasses) { Optimizer opt(SPV_ENV_WEBGPU_0); - opt.RegisterWebGPUPasses(); + opt.RegisterVulkanToWebGPUPasses(); std::vector pass_names = opt.GetPassNames(); std::vector registered_passes; @@ -246,7 +246,7 @@ TEST(Optimizer, WebGPUModeSetsCorrectPasses) { EXPECT_EQ(registered_passes[i], expected_passes[i]); } -struct WebGPUPassCase { +struct VulkanToWebGPUPassCase { // Input SPIR-V std::string input; // Expected result SPIR-V @@ -255,15 +255,16 @@ struct WebGPUPassCase { std::string pass; }; -using WebGPUPassTest = PassTest<::testing::TestWithParam>; +using VulkanToWebGPUPassTest = + PassTest<::testing::TestWithParam>; -TEST_P(WebGPUPassTest, Ran) { +TEST_P(VulkanToWebGPUPassTest, Ran) { SpirvTools tools(SPV_ENV_WEBGPU_0); std::vector binary; tools.Assemble(GetParam().input, &binary); Optimizer opt(SPV_ENV_WEBGPU_0); - opt.RegisterWebGPUPasses(); + opt.RegisterVulkanToWebGPUPasses(); std::vector optimized; class ValidatorOptions validator_options; @@ -277,8 +278,8 @@ TEST_P(WebGPUPassTest, Ran) { } INSTANTIATE_TEST_SUITE_P( - Optimizer, WebGPUPassTest, - ::testing::ValuesIn(std::vector{ + Optimizer, VulkanToWebGPUPassTest, + ::testing::ValuesIn(std::vector{ // FlattenDecorations {// input "OpCapability Shader\n" diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index 473dd75a..1566ac7f 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -79,9 +79,15 @@ std::string GetSizePasses() { return GetListOfPassesAsString(optimizer); } -std::string GetWebGPUPasses() { +std::string GetVulkanToWebGPUPasses() { spvtools::Optimizer optimizer(SPV_ENV_WEBGPU_0); - optimizer.RegisterWebGPUPasses(); + optimizer.RegisterVulkanToWebGPUPasses(); + return GetListOfPassesAsString(optimizer); +} + +std::string GetWebGPUToVulkanPasses() { + spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1); + optimizer.RegisterWebGPUToVulkanPasses(); return GetListOfPassesAsString(optimizer); } @@ -99,94 +105,119 @@ standard output. NOTE: The optimizer is a work in progress. -Options (in lexicographical order): +Options (in lexicographical order):)", + program, program); + printf(R"( --ccp Apply the conditional constant propagation transform. This will propagate constant values throughout the program, and simplify expressions and conditional jumps with known predicate values. Performed on entry point call tree functions and - exported functions. + exported functions.)"); + printf(R"( --cfg-cleanup Cleanup the control flow graph. This will remove any unnecessary code from the CFG like unreachable code. Performed on entry - point call tree functions and exported functions. + point call tree functions and exported functions.)"); + printf(R"( --combine-access-chains Combines chained access chains to produce a single instruction - where possible. + where possible.)"); + printf(R"( --compact-ids Remap result ids to a compact range starting from %%1 and without - any gaps. + any gaps.)"); + printf(R"( --convert-local-access-chains Convert constant index access chain loads/stores into equivalent load/stores with inserts and extracts. Performed on function scope variables referenced only with load, store, and constant index access chains in entry point call tree - functions. + functions.)"); + printf(R"( --copy-propagate-arrays Does propagation of memory references when an array is a copy of another. It will only propagate an array if the source is never - written to, and the only store to the target is the copy. + written to, and the only store to the target is the copy.)"); + printf(R"( --eliminate-common-uniform Perform load/load elimination for duplicate uniform values. Converts any constant index access chain uniform loads into its equivalent load and extract. Some loads will be moved to facilitate sharing. Performed only on entry point - call tree functions. + call tree functions.)"); + printf(R"( --eliminate-dead-branches Convert conditional branches with constant condition to the indicated unconditional brranch. Delete all resulting dead - code. Performed only on entry point call tree functions. + code. Performed only on entry point call tree functions.)"); + printf(R"( --eliminate-dead-code-aggressive Delete instructions which do not contribute to a function's - output. Performed only on entry point call tree functions. + output. Performed only on entry point call tree functions.)"); + printf(R"( --eliminate-dead-const - Eliminate dead constants. + Eliminate dead constants.)"); + printf(R"( --eliminate-dead-functions Deletes functions that cannot be reached from entry points or - exported functions. + exported functions.)"); + printf(R"( --eliminate-dead-inserts Deletes unreferenced inserts into composites, most notably unused stores to vector components, that are not removed by - aggressive dead code elimination. + aggressive dead code elimination.)"); + printf(R"( --eliminate-dead-variables - Deletes module scope variables that are not referenced. + Deletes module scope variables that are not referenced.)"); + printf(R"( --eliminate-insert-extract DEPRECATED. This pass has been replaced by the simplification pass, and that pass will be run instead. - See --simplify-instructions. + See --simplify-instructions.)"); + printf(R"( --eliminate-local-multi-store Replace stores and loads of function scope variables that are stored multiple times. Performed on variables referenceed only with loads and stores. Performed only on entry point call tree - functions. + functions.)"); + printf(R"( --eliminate-local-single-block Perform single-block store/load and load/load elimination. Performed only on function scope variables in entry point - call tree functions. + call tree functions.)"); + printf(R"( --eliminate-local-single-store Replace stores and loads of function scope variables that are only stored once. Performed on variables referenceed only with loads and stores. Performed only on entry point call tree - functions. + functions.)"); + printf(R"( --flatten-decorations Replace decoration groups with repeated OpDecorate and - OpMemberDecorate instructions. + OpMemberDecorate instructions.)"); + printf(R"( --fold-spec-const-op-composite Fold the spec constants defined by OpSpecConstantOp or OpSpecConstantComposite instructions to front-end constants - when possible. + when possible.)"); + printf(R"( --freeze-spec-const Freeze the values of specialization constants to their default - values. + values.)"); + printf(R"( --generate-webgpu-initializers Adds initial values to OpVariable instructions that are missing - them, due to their storage type requiring them for WebGPU. + them, due to their storage type requiring them for WebGPU.)"); + printf(R"( --if-conversion - Convert if-then-else like assignments into OpSelect. + Convert if-then-else like assignments into OpSelect.)"); + printf(R"( --inline-entry-points-exhaustive Exhaustively inline all function calls in entry point call tree functions. Currently does not inline calls to functions with - early return in a loop. + early return in a loop.)"); + printf(R"( --legalize-hlsl Runs a series of optimizations that attempts to take SPIR-V generated by an HLSL front-end and generates legal Vulkan SPIR-V. @@ -194,46 +225,58 @@ Options (in lexicographical order): %s Note this does not guarantee legal code. This option passes the - option --relax-logical-pointer to the validator. + option --relax-logical-pointer to the validator.)", + GetLegalizationPasses().c_str()); + printf(R"( --local-redundancy-elimination Looks for instructions in the same basic block that compute the - same value, and deletes the redundant ones. + same value, and deletes the redundant ones.)"); + printf(R"( --loop-fission Splits any top level loops in which the register pressure has exceeded a given threshold. The threshold must follow the use of - this flag and must be a positive integer value. + this flag and must be a positive integer value.)"); + printf(R"( --loop-fusion Identifies adjacent loops with the same lower and upper bound. If this is legal, then merge the loops into a single loop. Includes heuristics to ensure it does not increase number of registers too much, while reducing the number of loads from memory. Takes an additional positive integer argument to set - the maximum number of registers. + the maximum number of registers.)"); + printf(R"( --loop-invariant-code-motion Identifies code in loops that has the same value for every - iteration of the loop, and move it to the loop pre-header. + iteration of the loop, and move it to the loop pre-header.)"); + printf(R"( --loop-unroll - Fully unrolls loops marked with the Unroll flag + Fully unrolls loops marked with the Unroll flag)"); + printf(R"( --loop-unroll-partial Partially unrolls loops marked with the Unroll flag. Takes an additional non-0 integer argument to set the unroll factor, or - how many times a loop body should be duplicated + how many times a loop body should be duplicated)"); + printf(R"( --loop-peeling Execute few first (respectively last) iterations before - (respectively after) the loop if it can elide some branches. + (respectively after) the loop if it can elide some branches.)"); + printf(R"( --loop-peeling-threshold Takes a non-0 integer argument to set the loop peeling code size growth threshold. The threshold prevents the loop peeling from happening if the code size increase created by - the optimization is above the threshold. + the optimization is above the threshold.)"); + printf(R"( --max-id-bound= Sets the maximum value for the id bound for the moudle. The default is the minimum value for this limit, 0x3FFFFF. See - section 2.17 of the Spir-V specification. + section 2.17 of the Spir-V specification.)"); + printf(R"( --merge-blocks Join two blocks into a single block if the second has the first as its only predecessor. Performed only on entry point - call tree functions. + call tree functions.)"); + printf(R"( --merge-return Changes functions that have multiple return statements so they have a single return statement. @@ -247,17 +290,21 @@ Options (in lexicographical order): label and an OpBranch to the header, nothing else. These conditions are guaranteed to be met after running - dead-branch elimination. + dead-branch elimination.)"); + printf(R"( --loop-unswitch Hoists loop-invariant conditionals out of loops by duplicating the loop on each branch of the conditional and adjusting each - copy of the loop. + copy of the loop.)"); + printf(R"( -O Optimize for performance. Apply a sequence of transformations in an attempt to improve the performance of the generated code. For this version of the optimizer, this flag is equivalent to specifying the following optimization code names: - %s + %s)", + GetOptimizationPasses().c_str()); + printf(R"( -Os Optimize for size. Apply a sequence of transformations in an attempt to minimize the size of the generated code. For this @@ -266,7 +313,9 @@ Options (in lexicographical order): %s NOTE: The specific transformations done by -O and -Os change - from release to release. + from release to release.)", + GetSizePasses().c_str()); + printf(R"( -Oconfig= Apply the sequence of transformations indicated in . This file contains a sequence of strings separated by whitespace @@ -294,109 +343,148 @@ Options (in lexicographical order): that position in the command line. For example, the invocation 'spirv-opt --merge-blocks -O ...' applies the transformation --merge-blocks followed by all the transformations implied by - -O. + -O.)"); + printf(R"( --print-all Print SPIR-V assembly to standard error output before each pass - and after the last pass. + and after the last pass.)"); + printf(R"( --private-to-local Change the scope of private variables that are used in a single - function to that function. + function to that function.)"); + printf(R"( --reduce-load-size Replaces loads of composite objects where not every component is - used by loads of just the elements that are used. + used by loads of just the elements that are used.)"); + printf(R"( --redundancy-elimination Looks for instructions in the same function that compute the - same value, and deletes the redundant ones. + same value, and deletes the redundant ones.)"); + printf(R"( --relax-struct-store Allow store from one struct type to a different type with compatible layout and members. This option is forwarded to the - validator. + validator.)"); + printf(R"( --remove-duplicates Removes duplicate types, decorations, capabilities and extension - instructions. + instructions.)"); + printf(R"( --replace-invalid-opcode Replaces instructions whose opcode is valid for shader modules, but not for the current shader stage. To have an effect, all - entry points must have the same execution model. + entry points must have the same execution model.)"); + printf(R"( --ssa-rewrite Replace loads and stores to function local variables with - operations on SSA IDs. + operations on SSA IDs.)"); + printf(R"( --scalar-replacement[=] Replace aggregate function scope variables that are only accessed via their elements with new function variables representing each element. is a limit on the size of the aggragates that will be replaced. 0 means there is no limit. The default value is - 100. + 100.)"); + printf(R"( --set-spec-const-default-value ": ..." Set the default values of the specialization constants with : pairs specified in a double-quoted string. : pairs must be separated by blank spaces, and in each pair, spec id and default value must be separated with colon ':' without any blank spaces in between. - e.g.: --set-spec-const-default-value "1:100 2:400" + e.g.: --set-spec-const-default-value "1:100 2:400")"); + printf(R"( --simplify-instructions Will simplify all instructions in the function as much as - possible. + possible.)"); + printf(R"( --skip-validation Will not validate the SPIR-V before optimizing. If the SPIR-V is invalid, the optimizer may fail or generate incorrect code. - This options should be used rarely, and with caution. + This options should be used rarely, and with caution.)"); + printf(R"( --strength-reduction - Replaces instructions with equivalent and less expensive ones. + Replaces instructions with equivalent and less expensive ones.)"); + printf(R"( --strip-atomic-counter-memory - Removes AtomicCountMemory bit from memory semantics values. + Removes AtomicCountMemory bit from memory semantics values.)"); + printf(R"( --strip-debug - Remove all debug instructions. + Remove all debug instructions.)"); + printf(R"( --strip-reflect Remove all reflection information. For now, this covers - reflection information defined by SPV_GOOGLE_hlsl_functionality1. + reflection information defined by SPV_GOOGLE_hlsl_functionality1.)"); + printf(R"( --target-env= Set the target environment. Without this flag the target enviroment defaults to spv1.3. must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0, - spv1.1, spv1.2, spv1.3, or webgpu0. + spv1.1, spv1.2, spv1.3, or webgpu0.)"); + printf(R"( --time-report Print the resource utilization of each pass (e.g., CPU time, RSS) to standard error output. Currently it supports only Unix systems. This option is the same as -ftime-report in GCC. It prints CPU/WALL/USR/SYS time (and RSS if possible), but note that USR/SYS time are returned by getrusage() and can have a small - error. + error.)"); + printf(R"( --upgrade-memory-model Upgrades the Logical GLSL450 memory model to Logical VulkanKHR. Transforms memory, image, atomic and barrier operations to conform - to that model's requirements. + to that model's requirements.)"); + printf(R"( --vector-dce This pass looks for components of vectors that are unused, and removes them from the vector. Note this would still leave around - lots of dead code that a pass of ADCE will be able to remove. - --webgpu-mode - Turns on the prescribed passes for WebGPU and sets the target - environmet to webgpu0. Other passes may be turned on via - additional flags, but such combinations are not tested. + lots of dead code that a pass of ADCE will be able to remove.)"); + printf(R"( + --vulkan-to-webgpu + Turns on the prescribed passes for converting from Vulkan to + WebGPU and sets the target environment to webgpu0. Other passes + may be turned on via additional flags, but such combinations are + not tested. Using --target-env with this flag is not allowed. This flag is the equivalent of passing in --target-env=webgpu0 and specifying the following optimization code names: %s - NOTE: This flag is a WIP and its behaviour is subject to change. + NOTE: This flag is a WIP and its behaviour is subject to change.)", + GetVulkanToWebGPUPasses().c_str()); + printf(R"( + --webgpu-to-vulkan + Turns on the prescribed passes for converting from WebGPU to + Vulkan and sets the target environment to vulkan1.1. Other passes + may be turned on via additional flags, but such combinations are + not tested. + Using --target-env with this flag is not allowed. + + This flag is the equivalent of passing in --target-env=vulkan1.1 + and specifying the following optimization code names: + %s + + NOTE: This flag is a WIP and its behaviour is subject to change.)", + GetWebGPUToVulkanPasses().c_str()); + printf(R"( --workaround-1209 Rewrites instructions for which there are known driver bugs to avoid triggering those bugs. - Current workarounds: Avoid OpUnreachable in loops. + Current workarounds: Avoid OpUnreachable in loops.)"); + printf(R"( --unify-const - Remove the duplicated constants. + Remove the duplicated constants.)"); + printf(R"( --validate-after-all - Validate the module after each pass is performed. + Validate the module after each pass is performed.)"); + printf(R"( -h, --help - Print this help. + Print this help.)"); + printf(R"( --version Display optimizer version information. -)", - program, program, GetLegalizationPasses().c_str(), - GetOptimizationPasses().c_str(), GetSizePasses().c_str(), - GetWebGPUPasses().c_str()); +)"); } // Reads command-line flags the file specified in |oconfig_flag|. This string @@ -547,7 +635,8 @@ OptStatus ParseFlags(int argc, const char** argv, spvtools::OptimizerOptions* optimizer_options) { std::vector pass_flags; bool target_env_set = false; - bool webgpu_mode_set = false; + bool vulkan_to_webgpu_set = false; + bool webgpu_to_vulkan_set = false; for (int argi = 1; argi < argc; ++argi) { const char* cur_arg = argv[argi]; if ('-' == cur_arg[0]) { @@ -608,10 +697,16 @@ OptStatus ParseFlags(int argc, const char** argv, max_id_bound); } else if (0 == strncmp(cur_arg, "--target-env=", sizeof("--target-env=") - 1)) { - if (webgpu_mode_set) { + if (vulkan_to_webgpu_set) { spvtools::Error(opt_diagnostic, nullptr, {}, - "Cannot use both --webgpu-mode and --target-env at " - "the same time"); + "Cannot use both --vulkan-to-webgpu and --target-env " + "at the same time"); + return {OPT_STOP, 1}; + } + if (webgpu_to_vulkan_set) { + spvtools::Error(opt_diagnostic, nullptr, {}, + "Cannot use both --webgpu-to-vulkan and --target-env " + "at the same time"); return {OPT_STOP, 1}; } const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); @@ -623,16 +718,38 @@ OptStatus ParseFlags(int argc, const char** argv, return {OPT_STOP, 1}; } optimizer->SetTargetEnv(target_env); - } else if (0 == strcmp(cur_arg, "--webgpu-mode")) { + } else if (0 == strcmp(cur_arg, "--vulkan-to-webgpu")) { if (target_env_set) { spvtools::Error(opt_diagnostic, nullptr, {}, - "Cannot use both --webgpu-mode and --target-env at " - "the same time"); + "Cannot use both --vulkan-to-webgpu and --target-env " + "at the same time"); + return {OPT_STOP, 1}; + } + if (webgpu_to_vulkan_set) { + spvtools::Error(opt_diagnostic, nullptr, {}, + "Cannot use both --vulkan-to-webgpu and " + "--webgpu-to-vulkan at the same time"); return {OPT_STOP, 1}; } optimizer->SetTargetEnv(SPV_ENV_WEBGPU_0); - optimizer->RegisterWebGPUPasses(); + optimizer->RegisterVulkanToWebGPUPasses(); + } else if (0 == strcmp(cur_arg, "--webgpu-to-vulkan")) { + if (target_env_set) { + spvtools::Error(opt_diagnostic, nullptr, {}, + "Cannot use both --webgpu-to-vulkan and --target-env " + "at the same time"); + return {OPT_STOP, 1}; + } + if (vulkan_to_webgpu_set) { + spvtools::Error(opt_diagnostic, nullptr, {}, + "Cannot use both --webgpu-to-vulkan and " + "--vulkan-to-webgpu at the same time"); + return {OPT_STOP, 1}; + } + + optimizer->SetTargetEnv(SPV_ENV_VULKAN_1_1); + optimizer->RegisterWebGPUToVulkanPasses(); } else if (0 == strcmp(cur_arg, "--validate-after-all")) { optimizer->SetValidateAfterAll(true); } else {