From 784b1650c3820a230c7b28ec38f607f944c6aa4e Mon Sep 17 00:00:00 2001 From: bridgewaterrobbie Date: Tue, 18 Mar 2025 16:45:58 -0400 Subject: [PATCH] quick commit of combined image sampler fix testing --- libs/filamat/src/GLSLPostProcessor.cpp | 5 + third_party/spirv-tools/Android.mk | 2 + third_party/spirv-tools/BUILD.bazel | 1 + third_party/spirv-tools/BUILD.gn | 5 + third_party/spirv-tools/CHANGES | 35 + third_party/spirv-tools/DEPS | 10 +- third_party/spirv-tools/docs/syntax.md | 44 + .../include/spirv-tools/libspirv.h | 4 + .../include/spirv-tools/libspirv.hpp | 5 + .../include/spirv-tools/optimizer.hpp | 58 +- .../kokoro/check-format/build-docker.sh | 26 + .../spirv-tools/kokoro/check-format/build.sh | 39 +- .../spirv-tools/kokoro/scripts/linux/build.sh | 2 +- third_party/spirv-tools/source/diff/diff.cpp | 62 +- .../spirv-tools/source/link/linker.cpp | 6 +- .../spirv-tools/source/opt/CMakeLists.txt | 4 + .../source/opt/copy_prop_arrays.cpp | 1 + .../spirv-tools/source/opt/folding_rules.cpp | 186 +- third_party/spirv-tools/source/opt/function.h | 21 +- .../source/opt/inline_exhaustive_pass.cpp | 5 + .../spirv-tools/source/opt/inline_pass.cpp | 65 + .../spirv-tools/source/opt/inline_pass.h | 11 + .../spirv-tools/source/opt/ir_builder.h | 73 +- .../spirv-tools/source/opt/ir_context.cpp | 1 + .../spirv-tools/source/opt/optimizer.cpp | 20 +- third_party/spirv-tools/source/opt/passes.h | 2 + .../opt/resolve_binding_conflicts_pass.cpp | 328 +++ .../opt/resolve_binding_conflicts_pass.h | 38 + .../source/opt/scalar_replacement_pass.h | 2 +- .../opt/split_combined_image_sampler_pass.cpp | 641 ++++++ .../opt/split_combined_image_sampler_pass.h | 169 ++ .../source/opt/trim_capabilities_pass.cpp | 34 +- .../source/opt/trim_capabilities_pass.h | 1 + .../spirv-tools/source/opt/type_manager.cpp | 2 + .../source/spirv_validator_options.cpp | 5 + .../source/spirv_validator_options.h | 2 + third_party/spirv-tools/source/text.cpp | 128 ++ .../spirv-tools/source/text_handler.cpp | 3 + .../spirv-tools/source/util/small_vector.h | 1 + .../spirv-tools/source/util/string_utils.h | 14 +- .../source/val/validate_bitwise.cpp | 4 +- .../source/val/validate_decorations.cpp | 201 +- .../source/val/validate_function.cpp | 3 +- .../source/val/validate_interfaces.cpp | 191 +- .../source/val/validate_memory.cpp | 43 +- .../source/val/validation_state.cpp | 6 +- third_party/spirv-tools/test/CMakeLists.txt | 1 + .../diff_files/diff_test_files_autogen.cmake | 3 + .../function_group_by_full_type_autogen.cpp | 194 ++ .../function_group_by_full_type_dst.spvasm | 25 + .../function_group_by_full_type_src.spvasm | 25 + .../function_group_by_mapped_id_autogen.cpp | 181 ++ .../function_group_by_mapped_id_dst.spvasm | 23 + .../function_group_by_mapped_id_src.spvasm | 23 + .../diff_files/string_in_ext_inst_autogen.cpp | 205 ++ .../diff_files/string_in_ext_inst_dst.spvasm | 25 + .../diff_files/string_in_ext_inst_src.spvasm | 26 + ...ormation_set_memory_operands_mask_test.cpp | 74 +- .../spirv-tools/test/link/linker_fixture.h | 23 +- .../link/matching_imports_to_exports_test.cpp | 14 + .../spirv-tools/test/op_unknown_test.cpp | 128 ++ .../spirv-tools/test/opt/CMakeLists.txt | 2 + .../opt/aggressive_dead_code_elim_test.cpp | 179 ++ .../test/opt/copy_prop_array_test.cpp | 80 + .../spirv-tools/test/opt/fold_test.cpp | 296 ++- .../spirv-tools/test/opt/inline_test.cpp | 84 +- .../spirv-tools/test/opt/ir_builder.cpp | 32 + .../spirv-tools/test/opt/optimizer_test.cpp | 118 ++ .../spirv-tools/test/opt/pass_fixture.h | 20 + third_party/spirv-tools/test/opt/pass_utils.h | 16 + .../resolve_binding_conflicts_pass_test.cpp | 864 ++++++++ .../test/opt/scalar_replacement_test.cpp | 2 +- ...split_combined_image_sampler_pass_test.cpp | 1868 +++++++++++++++++ .../test/opt/trim_capabilities_pass_test.cpp | 238 +++ .../spirv-tools/test/string_utils_test.cpp | 53 +- .../spirv-tools/test/tools/opt/flags.py | 6 +- .../spirv-tools/test/val/val_bitwise_test.cpp | 16 +- .../test/val/val_builtins_test.cpp | 10 - .../test/val/val_decoration_test.cpp | 305 ++- .../spirv-tools/test/val/val_id_test.cpp | 4 +- .../test/val/val_interfaces_test.cpp | 167 +- .../spirv-tools/test/val/val_memory_test.cpp | 284 ++- third_party/spirv-tools/tools/opt/opt.cpp | 17 +- third_party/spirv-tools/tools/util/flags.cpp | 1 + third_party/spirv-tools/tools/val/val.cpp | 4 + .../spirv-tools/utils/check_code_format.sh | 29 +- 86 files changed, 7880 insertions(+), 294 deletions(-) create mode 100755 third_party/spirv-tools/kokoro/check-format/build-docker.sh mode change 100644 => 100755 third_party/spirv-tools/kokoro/check-format/build.sh create mode 100644 third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.cpp create mode 100644 third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.h create mode 100644 third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.cpp create mode 100644 third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.h create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_autogen.cpp create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_dst.spvasm create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_src.spvasm create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_autogen.cpp create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_dst.spvasm create mode 100644 third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_src.spvasm create mode 100644 third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_autogen.cpp create mode 100644 third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_dst.spvasm create mode 100644 third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_src.spvasm create mode 100644 third_party/spirv-tools/test/op_unknown_test.cpp create mode 100644 third_party/spirv-tools/test/opt/resolve_binding_conflicts_pass_test.cpp create mode 100644 third_party/spirv-tools/test/opt/split_combined_image_sampler_pass_test.cpp diff --git a/libs/filamat/src/GLSLPostProcessor.cpp b/libs/filamat/src/GLSLPostProcessor.cpp index 75e819bca6..594eb9d20a 100644 --- a/libs/filamat/src/GLSLPostProcessor.cpp +++ b/libs/filamat/src/GLSLPostProcessor.cpp @@ -535,6 +535,7 @@ bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) { //TODO remove this if block once combined image sampler conversion works. if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) { slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl; + return false; } else { slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl; @@ -919,6 +920,10 @@ std::shared_ptr GLSLPostProcessor::createOptimizer( optimizer->RegisterPass(CreateRedundancyEliminationPass()); optimizer->RegisterPass(CreateAggressiveDCEPass()); } + if (config.targetApi == MaterialBuilder::TargetApi::WEBGPU) { + optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass()); + optimizer->RegisterPass(CreateResolveBindingConflictsPass()); + } return optimizer; } diff --git a/third_party/spirv-tools/Android.mk b/third_party/spirv-tools/Android.mk index 1414b52497..b2b96d9995 100644 --- a/third_party/spirv-tools/Android.mk +++ b/third_party/spirv-tools/Android.mk @@ -172,11 +172,13 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/remove_unused_interface_variables_pass.cpp \ source/opt/replace_desc_array_access_using_var_index.cpp \ source/opt/replace_invalid_opc.cpp \ + source/opt/resolve_binding_conflicts_pass.cpp \ source/opt/scalar_analysis.cpp \ source/opt/scalar_analysis_simplification.cpp \ source/opt/scalar_replacement_pass.cpp \ source/opt/set_spec_constant_default_value_pass.cpp \ source/opt/simplification_pass.cpp \ + source/opt/split_combined_image_sampler_pass.cpp \ source/opt/spread_volatile_semantics.cpp \ source/opt/ssa_rewrite_pass.cpp \ source/opt/strength_reduction_pass.cpp \ diff --git a/third_party/spirv-tools/BUILD.bazel b/third_party/spirv-tools/BUILD.bazel index 526d08e39f..2cbac90df5 100644 --- a/third_party/spirv-tools/BUILD.bazel +++ b/third_party/spirv-tools/BUILD.bazel @@ -201,6 +201,7 @@ cc_library( deps = [ ":spirv_tools_internal", "@spirv_headers//:spirv_common_headers", + "@spirv_headers//:spirv_c_headers", ], ) diff --git a/third_party/spirv-tools/BUILD.gn b/third_party/spirv-tools/BUILD.gn index 7c361f0337..5fd00ca0a3 100644 --- a/third_party/spirv-tools/BUILD.gn +++ b/third_party/spirv-tools/BUILD.gn @@ -771,6 +771,8 @@ static_library("spvtools_opt") { "source/opt/replace_desc_array_access_using_var_index.h", "source/opt/replace_invalid_opc.cpp", "source/opt/replace_invalid_opc.h", + "source/opt/resolve_binding_conflicts_pass.cpp", + "source/opt/resolve_binding_conflicts_pass.h", "source/opt/scalar_analysis.cpp", "source/opt/scalar_analysis.h", "source/opt/scalar_analysis_nodes.h", @@ -781,6 +783,8 @@ static_library("spvtools_opt") { "source/opt/set_spec_constant_default_value_pass.h", "source/opt/simplification_pass.cpp", "source/opt/simplification_pass.h", + "source/opt/split_combined_image_sampler_pass.cpp", + "source/opt/split_combined_image_sampler_pass.h", "source/opt/spread_volatile_semantics.cpp", "source/opt/spread_volatile_semantics.h", "source/opt/ssa_rewrite_pass.cpp", @@ -1382,6 +1386,7 @@ if (build_with_chromium && spvtools_build_executables) { "test/libspirv_macros_test.cpp", "test/name_mapper_test.cpp", "test/named_id_test.cpp", + "test/op_unknown_test.cpp", "test/opcode_make_test.cpp", "test/opcode_require_capabilities_test.cpp", "test/opcode_split_test.cpp", diff --git a/third_party/spirv-tools/CHANGES b/third_party/spirv-tools/CHANGES index 007e9010a3..1aad4d358c 100644 --- a/third_party/spirv-tools/CHANGES +++ b/third_party/spirv-tools/CHANGES @@ -1,5 +1,40 @@ Revision history for SPIRV-Tools +v2025.1 2025-02-28 + - General + - diff: Fix crash in OpString matching (#5988) + - Add SPV_AMDX_shader_enqueue version 2 support (#5838) + - add support for SPV_INTEL_subgroup_matrix_multiply_accumulate (#5928) + - update cmake_minimum_required to 3.22.1 (#5925) + - Add OpImageSampleFootprintNV to IsAllowedSampledImageOperand (#5914) + - assembler: ensure progress when seeking the version string (#5910) + - Optimizer + - opt: keep all OpSource instructions (#5901) + - [opt] Fix bug opt::InstructionBuilder::AddVariable (#6007) + - [OPT] Add SPV_KHR_ray_tracing to allow list (#5941) + - opt: keep all OpSource instructions (#5901) + - Validator + - spirv-val: Add AllowVulkan32BitBitwise option (#6001) + - Fix untyped pointer comparison validation (#6004) + - spirv-val: Update VUIDs for 308 header (#5990) + - spirv-val: fix env parsing for vk1.1spv1.4 (#5985) + - Add validation for SPV_NV_linear_swept_spheres. (#5975) + - Add validation SPV_NV_cluster_acceleration_structure. (#5974) + - Improve the instruction diagnostic for some access chain errors (#5978) + - Update physical storage buffer validation to match SPIR-V 1.6.5 (#5977) + - Validate SPV_NV_cooperative_vector (#5972) + - Fix layout checks with untyped pointers (#5970) + - spirv-val: Update mislabeled VUIDs (#5964) + - More explicit layout validation (#5958) + - spirv-val: Add VK_KHR_maintenance8 support (#5951) + - Add EXT_mesh_shader validation support (#5640) + - spirv-val: Remove OpenCL ivec3 req (#5940) + - spirv-val: Validate zero product workgroup size (#5407) + - Relax DebugLine validation (#5916) + - Linker + - linker: remove LinkOnceODR decorations when linking executables (#5979) + - fix: handle LinkOnceODR correctly (#5938) + v2024.4 2024-12-04 - General - Add FPEncoding operand type. (#5726) diff --git a/third_party/spirv-tools/DEPS b/third_party/spirv-tools/DEPS index b7f57b11d4..23511c9cb9 100644 --- a/third_party/spirv-tools/DEPS +++ b/third_party/spirv-tools/DEPS @@ -3,18 +3,18 @@ use_relative_paths = True vars = { 'github': 'https://github.com', - 'abseil_revision': '69b58f9227bf91a9315475579df15fbb7c5c612e', + 'abseil_revision': '310e6f4f0f202da13720fdd6cb0095e10a98fe1c', - 'effcee_revision': '12241cbc30f20730b656db7fd5a3fa36cd420843', + 'effcee_revision': '874b47102c57a8979c0f154cf8e0eab53c0a0502', - 'googletest_revision': 'e235eb34c6c4fed790ccdad4b16394301360dcd4', + 'googletest_revision': '4902ea2d7c6faed89b6facee00baa34bb108fc0d', # Use protobufs before they gained the dependency on abseil 'protobuf_revision': 'v21.12', - 're2_revision': '6dcd83d60f7944926bfd308cc13979fc53dd69ca', + 're2_revision': 'c84a140c93352cdabbfb547c531be34515b12228', - 'spirv_headers_revision': 'e7294a8ebed84f8c5bd3686c68dbe12a4e65b644', + 'spirv_headers_revision': 'ddd2c099be25e3fec7cdc14106c17aca99512082', } deps = { diff --git a/third_party/spirv-tools/docs/syntax.md b/third_party/spirv-tools/docs/syntax.md index c135d01072..705f344f26 100644 --- a/third_party/spirv-tools/docs/syntax.md +++ b/third_party/spirv-tools/docs/syntax.md @@ -215,6 +215,50 @@ Note that this has some interesting consequences, including: that named ID being output. This may be valid SPIR-V, contrary to the presumed intention of the writer. +## OpUnknown + + +HLSL has a feature that allows users to specify an exact SPIR-V type using [the +`SpirvType` template](https://github.com/microsoft/hlsl-specs/blob/main/proposals/0011-inline-spirv.md#types). +This feature allows the user to specify a type opcode that the compiler does +not know. In this case, it will be unable to generate assembly using the +correct mnemonic. + +In order to represent unknown opcodes in assembly format, the `OpUnknown` +pseudo-instruction may be used. The syntax is: + +``` +OpUnknown(, ) ... +``` + +`enumerant` is the opcode enumerant, and `WordCount` is the number of words in +the instruction. These will be assembled into a single word representing the +opcode. Operands will be parsed according to the alternate parsing mode +described in [Arbitrary Integers](#op-unknown). Named enumerated values cannot +be handled by this mode and must be represented using the arbitrary integer +syntax. + +It must be used at the beginning of a new instruction, and if there is a result +ID it must explicitly be passed in as an operand. This is because the physical +layout of a SPIR-V instruction may include a result type operand before the +result ID operand, but it depends on the opcode and cannot be inferred for an +unknown operand. + +For example, a 32-bit signed integer type could be represented like this: + +``` +OpUnknown(21, 4) %int_t 32 1 +``` + +An OpStore instruction could be represented as: + +``` +OpUnknown(62, 3) %9 %12 +``` + +The enumerant and word count must be decimal integers. + + ## Notes * Some enumerants cannot be used by name, because the target instruction diff --git a/third_party/spirv-tools/include/spirv-tools/libspirv.h b/third_party/spirv-tools/include/spirv-tools/libspirv.h index 2ac36a6a1f..bcc8332dc0 100644 --- a/third_party/spirv-tools/include/spirv-tools/libspirv.h +++ b/third_party/spirv-tools/include/spirv-tools/libspirv.h @@ -740,6 +740,10 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowLocalSizeId( SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowOffsetTextureOperand( spv_validator_options options, bool val); +// Allow base operands of some bit operations to be non-32-bit wide. +SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetAllowVulkan32BitBitwise( + spv_validator_options options, bool val); + // Whether friendly names should be used in validation error messages. SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetFriendlyNames( spv_validator_options options, bool val); diff --git a/third_party/spirv-tools/include/spirv-tools/libspirv.hpp b/third_party/spirv-tools/include/spirv-tools/libspirv.hpp index 1a75868a02..2cbb74927f 100644 --- a/third_party/spirv-tools/include/spirv-tools/libspirv.hpp +++ b/third_party/spirv-tools/include/spirv-tools/libspirv.hpp @@ -132,6 +132,11 @@ class SPIRV_TOOLS_EXPORT ValidatorOptions { spvValidatorOptionsSetAllowOffsetTextureOperand(options_, val); } + // Allow base operands of some bit operations to be non-32-bit wide. + void SetAllowVulkan32BitBitwise(bool val) { + spvValidatorOptionsSetAllowVulkan32BitBitwise(options_, val); + } + // Records whether or not the validator should relax the rules on pointer // usage in logical addressing mode. // diff --git a/third_party/spirv-tools/include/spirv-tools/optimizer.hpp b/third_party/spirv-tools/include/spirv-tools/optimizer.hpp index 9427b01eb3..5fb9a339c0 100644 --- a/third_party/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/third_party/spirv-tools/include/spirv-tools/optimizer.hpp @@ -240,7 +240,7 @@ class SPIRV_TOOLS_EXPORT Optimizer { private: struct SPIRV_TOOLS_LOCAL Impl; // Opaque struct for holding internal data. - std::unique_ptr impl_; // Unique pointer to internal data. + std::unique_ptr impl_; // Unique pointer to internal data. }; // Creates a null pass. @@ -655,7 +655,7 @@ Optimizer::PassToken CreateRedundancyEliminationPass(); // element if those elements are accessed individually. The parameter is a // limit on the number of members in the composite variable that the pass will // consider replacing. -Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 100); +Optimizer::PassToken CreateScalarReplacementPass(uint32_t size_limit = 0); // Create a private to local pass. // This pass looks for variables declared in the private storage class that are @@ -968,6 +968,60 @@ Optimizer::PassToken CreateInvocationInterlockPlacementPass(); // Creates a pass to add/remove maximal reconvergence execution mode. // This pass either adds or removes maximal reconvergence from all entry points. Optimizer::PassToken CreateModifyMaximalReconvergencePass(bool add); + +// Creates a pass to split combined image+sampler variables and function +// parameters into separate image and sampler parts. Binding numbers and +// other decorations are copied. +Optimizer::PassToken CreateSplitCombinedImageSamplerPass(); + +// Creates a pass to remap bindings to avoid conflicts, assuming the module +// is valid for Vulkan. A conflict exits when an entry point uses two distinct +// variables with the same descriptor set and binding. Vulkan allows one kind +// of conflict: when one varible is an image (or array of images), and the +// other is a sampler (or an array of samplers). + +// Conflicts are eliminated by incrementing the binding number of the sampler +// part, and then propagating that increment through variables with +// higher-numbered bindings until no conflict remains. This handles the case +// when multiple shaders may share the same resource variables; this can +// introduce holes in binding slots. +// +// Here's an example where shaders Alpha, Beta, Gamma, Delta collectively use +// resource variables %100, %101, %102, %103, %104 all with the same +// DescriptorSet and with Bindings as in the following table: +// +// Before: +// +// Binding: 0 1 2 3 +// Alpha: %100,%101 +// Beta: %100 %102 +// Gamma: %102 %103 +// Delta: %103 %104 +// +// The Alpha shader has a conflict where variables %100, %101 have the same +// descriptor set and binding. If %100 is a sampler resource variable, then +// the conflict is resolved by incrementing the binding number on %100 from 0 +// to 1. But this causes a new confict for shader Beta because it now uses +// both %100 and %102 with binding number 1. That conflict is resolved by +// incrementing the binding number on its variable that originally appeared +// second (i.e. %102), so %102 gets binding 2. This now produces a conflict +// for Gamma between %102 and %103 using binding number 2. Since %103 originally +// appeared second (in the view from Gamma), the algorithm bumps %103 to binding +// number %103. Now Delta has a conflict between %103 and %104, resulting in +// %104 getting the next binding number, 4. The picture afterward is: +// +// After: +// +// Binding: 0 1 2 3 4 +// Alpha: %101 %100 +// Beta: %100 %102 +// Gamma: %102 %103 +// Delta: %103 %104 +// +// +// This pass assumes binding numbers are not applid via decoration groups +// (OpDecorationGroup). +Optimizer::PassToken CreateResolveBindingConflictsPass(); } // namespace spvtools #endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_ diff --git a/third_party/spirv-tools/kokoro/check-format/build-docker.sh b/third_party/spirv-tools/kokoro/check-format/build-docker.sh new file mode 100755 index 0000000000..b7af9ee29b --- /dev/null +++ b/third_party/spirv-tools/kokoro/check-format/build-docker.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright (c) 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fail on any error. +set -e + +# This is required to run any git command in the docker since owner will +# have changed between the clone environment, and the docker container. +# Marking the root of the repo as safe for ownership changes. +git config --global --add safe.directory "$PWD" + +echo $(date): Check formatting... +./utils/check_code_format.sh ${1:-main} +echo $(date): check completed. diff --git a/third_party/spirv-tools/kokoro/check-format/build.sh b/third_party/spirv-tools/kokoro/check-format/build.sh old mode 100644 new mode 100755 index 96603e4476..6f073c64e8 --- a/third_party/spirv-tools/kokoro/check-format/build.sh +++ b/third_party/spirv-tools/kokoro/check-format/build.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Copyright (c) 2018 Google LLC. +# Copyright (c) 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,37 +12,16 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# Android Build Script. # Fail on any error. set -e -# Display commands being run. -set -x -BUILD_ROOT=$PWD -SRC=$PWD/github/SPIRV-Tools +SCRIPT_DIR="$( cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd )" +SRC_ROOT="$( cd "${SCRIPT_DIR}/../.." >/dev/null 2>&1 && pwd )" +TARGET_BRANCH="${KOKORO_GITHUB_PULL_REQUEST_TARGET_BRANCH-main}" -# This is required to run any git command in the docker since owner will -# have changed between the clone environment, and the docker container. -# Marking the root of the repo as safe for ownership changes. -git config --global --add safe.directory $SRC - -# Get clang-format-5.0.0. -# Once kokoro upgrades the Ubuntu VMs, we can use 'apt-get install clang-format' -curl -L http://releases.llvm.org/5.0.0/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04.tar.xz -o clang-llvm.tar.xz -tar xf clang-llvm.tar.xz -export PATH=$PWD/clang+llvm-5.0.0-linux-x86_64-ubuntu14.04/bin:$PATH - -cd $SRC -git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Headers external/spirv-headers -git clone https://github.com/google/googletest external/googletest -cd external && cd googletest && git reset --hard 1fb1bb23bb8418dc73a5a9a82bbed31dc610fec7 && cd .. && cd .. -git clone --depth=1 https://github.com/google/effcee external/effcee -git clone --depth=1 https://github.com/google/re2 external/re2 -# The --fail flag causes the command to fail on HTTP error response codes, like 404. -curl -L --fail https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o utils/clang-format-diff.py - -echo $(date): Check formatting... -./utils/check_code_format.sh; -echo $(date): check completed. +docker run --rm -i \ + --volume "${SRC_ROOT}:${SRC_ROOT}" \ + --workdir "${SRC_ROOT}" \ + "us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/formatter" \ + "${SCRIPT_DIR}/build-docker.sh" "${TARGET_BRANCH}" diff --git a/third_party/spirv-tools/kokoro/scripts/linux/build.sh b/third_party/spirv-tools/kokoro/scripts/linux/build.sh index 688ba7933d..cb6cf58cb6 100644 --- a/third_party/spirv-tools/kokoro/scripts/linux/build.sh +++ b/third_party/spirv-tools/kokoro/scripts/linux/build.sh @@ -52,7 +52,7 @@ docker run --rm -i \ --env KOKORO_ARTIFACTS_DIR="${KOKORO_ARTIFACTS_DIR}" \ --env BUILD_SHA="${BUILD_SHA}" \ --entrypoint "${SCRIPT_DIR}/build-docker.sh" \ - "gcr.io/shaderc-build/radial-build:latest" + us-east4-docker.pkg.dev/shaderc-build/radial-docker/ubuntu-24.04-amd64/cpp-builder RESULT=$? # This is important. If the permissions are not fixed, kokoro will fail diff --git a/third_party/spirv-tools/source/diff/diff.cpp b/third_party/spirv-tools/source/diff/diff.cpp index 6269af5004..2e455e5a7e 100644 --- a/third_party/spirv-tools/source/diff/diff.cpp +++ b/third_party/spirv-tools/source/diff/diff.cpp @@ -67,7 +67,9 @@ void CompactIds(std::vector& ids) { ids.resize(write_index); } -// A mapping between src and dst ids. +// A mapping from ids in one module to ids in the other. +// +// Differ contains two of these, for src->dst and dst->src. class IdMap { public: IdMap(size_t id_bound) { id_map_.resize(id_bound, 0); } @@ -190,6 +192,7 @@ class SrcDstIdMap { IdMap dst_to_src_; }; +// Mappings from ids to instructions and metadata, for a single module's ids. struct IdInstructions { IdInstructions(const opt::Module* module) : inst_map_(module->IdBound(), nullptr), @@ -198,6 +201,10 @@ struct IdInstructions { forward_pointer_map_(module->IdBound()) { // Map ids from all sections to instructions that define them. MapIdsToInstruction(module->ext_inst_imports()); + MapIdsToInstruction(module->debugs1()); + MapIdsToInstruction(module->debugs2()); + MapIdsToInstruction(module->debugs3()); + MapIdsToInstruction(module->ext_inst_debuginfo()); MapIdsToInstruction(module->types_values()); for (const opt::Function& function : *module) { function.ForEachInst( @@ -321,6 +328,8 @@ class Differ { // Get various properties from an id. These Helper functions are passed to // `GroupIds` and `GroupIdsAndMatch` below (as the `get_group` argument). uint32_t GroupIdsHelperGetTypeId(const IdInstructions& id_to, uint32_t id); + uint32_t GroupIdsHelperGetFunctionTypeId(const IdInstructions& id_to, + uint32_t id); spv::StorageClass GroupIdsHelperGetTypePointerStorageClass( const IdInstructions& id_to, uint32_t id); spv::Op GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to, @@ -883,6 +892,17 @@ uint32_t Differ::GroupIdsHelperGetTypeId(const IdInstructions& id_to, return GetInst(id_to, id)->type_id(); } +// Return an `OpFunction` instruction's full `OpTypeFunction` type, +// which includes parameter types. +// +// `GroupIdsHelperGetTypeId` applied to an `OpFunction` only gets the +// function's return type, so this is a slightly more precise way to +// match up functions by signature. +uint32_t Differ::GroupIdsHelperGetFunctionTypeId(const IdInstructions& id_to, + uint32_t id) { + return GetInst(id_to, id)->GetSingleWordOperand(3); +} + spv::StorageClass Differ::GroupIdsHelperGetTypePointerStorageClass( const IdInstructions& id_to, uint32_t id) { const opt::Instruction* inst = GetInst(id_to, id); @@ -902,6 +922,24 @@ spv::Op Differ::GroupIdsHelperGetTypePointerTypeOp(const IdInstructions& id_to, return type_inst->opcode(); } +// Group unmatched ids in `ids` according to some characteristic, +// determined by `get_group`. +// +// Using `get_group` to compute some sort of key for each id, set +// `groups` to map each key to all the ids that have that key. +// +// For example, to group ids by name, pass `Differ::GetName` as +// `get_group`. This will fill `groups` with a map from each name to +// all the ids with that name. +// +// Under the assumption that we're trying to establish new pairings, +// ids that are already paired are omitted from `groups`. +// +// The `is_src` parameter indicates whether `ids` are drawn from the +// source module or the destination module. +// +// The template parameter `T` is the key type, like `std::string` or +// `uint32_t`. template void Differ::GroupIds(const IdGroup& ids, bool is_src, std::map* groups, @@ -924,6 +962,10 @@ void Differ::GroupIds(const IdGroup& ids, bool is_src, } } +// Group `src_ids` and `dst_ids` according to `get_group`, and then use +// `match_group` to pair up ids in corresponding groups. +// +// Don't try to pair ids in groups whose key is `invalid_group_key`. template void Differ::GroupIdsAndMatch( const IdGroup& src_ids, const IdGroup& dst_ids, T invalid_group_key, @@ -2483,7 +2525,7 @@ void Differ::MatchFunctions() { // If there are multiple functions with the same name, group them by // type, and match only if the types match (and are unique). - GroupIdsAndMatch(src_group, dst_group, 0, + GroupIdsAndMatchByMappedId(src_group, dst_group, &Differ::GroupIdsHelperGetTypeId, [this](const IdGroup& src_group_by_type_id, const IdGroup& dst_group_by_type_id) { @@ -2526,9 +2568,19 @@ void Differ::MatchFunctions() { dst_match_result, 0); } - // Best effort match functions with matching type. - GroupIdsAndMatch( - src_func_ids, dst_func_ids, 0, &Differ::GroupIdsHelperGetTypeId, + // Best effort match functions with matching return and argument types. + GroupIdsAndMatchByMappedId( + src_func_ids, dst_func_ids, &Differ::GroupIdsHelperGetFunctionTypeId, + [this](const IdGroup& src_group_by_func_type_id, + const IdGroup& dst_group_by_func_type_id) { + BestEffortMatchFunctions(src_group_by_func_type_id, + dst_group_by_func_type_id, src_func_insts_, + dst_func_insts_); + }); + + // Best effort match functions with matching return types. + GroupIdsAndMatchByMappedId( + src_func_ids, dst_func_ids, &Differ::GroupIdsHelperGetTypeId, [this](const IdGroup& src_group_by_type_id, const IdGroup& dst_group_by_type_id) { BestEffortMatchFunctions(src_group_by_type_id, dst_group_by_type_id, diff --git a/third_party/spirv-tools/source/link/linker.cpp b/third_party/spirv-tools/source/link/linker.cpp index d030e2afbc..de305255d5 100644 --- a/third_party/spirv-tools/source/link/linker.cpp +++ b/third_party/spirv-tools/source/link/linker.cpp @@ -676,8 +676,10 @@ spv_result_t RemoveLinkageSpecificInstructions( if (inst->opcode() == spv::Op::OpDecorate && spv::Decoration(inst->GetSingleWordOperand(1u)) == spv::Decoration::LinkageAttributes && - spv::LinkageType(inst->GetSingleWordOperand(3u)) == - spv::LinkageType::Export) { + (spv::LinkageType(inst->GetSingleWordOperand(3u)) == + spv::LinkageType::Export || + spv::LinkageType(inst->GetSingleWordOperand(3u)) == + spv::LinkageType::LinkOnceODR)) { linked_context->KillInst(&*inst); } } diff --git a/third_party/spirv-tools/source/opt/CMakeLists.txt b/third_party/spirv-tools/source/opt/CMakeLists.txt index 4a12e6ef5f..ee3aeb1975 100644 --- a/third_party/spirv-tools/source/opt/CMakeLists.txt +++ b/third_party/spirv-tools/source/opt/CMakeLists.txt @@ -109,11 +109,13 @@ set(SPIRV_TOOLS_OPT_SOURCES remove_unused_interface_variables_pass.h replace_desc_array_access_using_var_index.h replace_invalid_opc.h + resolve_binding_conflicts_pass.h scalar_analysis.h scalar_analysis_nodes.h scalar_replacement_pass.h set_spec_constant_default_value_pass.h simplification_pass.h + split_combined_image_sampler_pass.h spread_volatile_semantics.h ssa_rewrite_pass.h strength_reduction_pass.h @@ -225,11 +227,13 @@ set(SPIRV_TOOLS_OPT_SOURCES remove_unused_interface_variables_pass.cpp replace_desc_array_access_using_var_index.cpp replace_invalid_opc.cpp + resolve_binding_conflicts_pass.cpp scalar_analysis.cpp scalar_analysis_simplification.cpp scalar_replacement_pass.cpp set_spec_constant_default_value_pass.cpp simplification_pass.cpp + split_combined_image_sampler_pass.cpp spread_volatile_semantics.cpp ssa_rewrite_pass.cpp strength_reduction_pass.cpp diff --git a/third_party/spirv-tools/source/opt/copy_prop_arrays.cpp b/third_party/spirv-tools/source/opt/copy_prop_arrays.cpp index 0a42074405..b3f0651643 100644 --- a/third_party/spirv-tools/source/opt/copy_prop_arrays.cpp +++ b/third_party/spirv-tools/source/opt/copy_prop_arrays.cpp @@ -276,6 +276,7 @@ CopyPropagateArrays::GetSourceObjectIfAny(uint32_t result) { case spv::Op::OpCompositeConstruct: return BuildMemoryObjectFromCompositeConstruct(result_inst); case spv::Op::OpCopyObject: + case spv::Op::OpCopyLogical: return GetSourceObjectIfAny(result_inst->GetSingleWordInOperand(0)); case spv::Op::OpCompositeInsert: return BuildMemoryObjectFromInsert(result_inst); diff --git a/third_party/spirv-tools/source/opt/folding_rules.cpp b/third_party/spirv-tools/source/opt/folding_rules.cpp index e5ac2a1cb2..e14e76b97b 100644 --- a/third_party/spirv-tools/source/opt/folding_rules.cpp +++ b/third_party/spirv-tools/source/opt/folding_rules.cpp @@ -2454,6 +2454,31 @@ FoldingRule RedundantFDiv() { FloatConstantKind kind0 = getFloatConstantKind(constants[0]); FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + if (kind0 == FloatConstantKind::Zero || kind1 == FloatConstantKind::One) { + inst->SetOpcode(spv::Op::OpCopyObject); + inst->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); + return true; + } + + return false; + }; +} + +FoldingRule RedundantFMod() { + return [](IRContext* context, Instruction* inst, + const std::vector& constants) { + assert(inst->opcode() == spv::Op::OpFMod && + "Wrong opcode. Should be OpFMod."); + assert(constants.size() == 2); + + if (!inst->IsFloatingPointFoldingAllowed()) { + return false; + } + + FloatConstantKind kind0 = getFloatConstantKind(constants[0]); + FloatConstantKind kind1 = getFloatConstantKind(constants[1]); + if (kind0 == FloatConstantKind::Zero) { inst->SetOpcode(spv::Op::OpCopyObject); inst->SetInOperands( @@ -2462,9 +2487,15 @@ FoldingRule RedundantFDiv() { } if (kind1 == FloatConstantKind::One) { + auto type = context->get_type_mgr()->GetType(inst->type_id()); + std::vector zero_words; + zero_words.resize(ElementWidth(type) / 32); + auto const_mgr = context->get_constant_mgr(); + auto zero = const_mgr->GetConstant(type, std::move(zero_words)); + auto zero_id = const_mgr->GetDefiningInstruction(zero)->result_id(); + inst->SetOpcode(spv::Op::OpCopyObject); - inst->SetInOperands( - {{SPV_OPERAND_TYPE_ID, {inst->GetSingleWordInOperand(0)}}}); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}}); return true; } @@ -2507,24 +2538,18 @@ FoldingRule RedundantFMix() { }; } -// This rule handles addition of zero for integers. -FoldingRule RedundantIAdd() { - return [](IRContext* context, Instruction* inst, - const std::vector& constants) { - assert(inst->opcode() == spv::Op::OpIAdd && - "Wrong opcode. Should be OpIAdd."); +// Returns a folding rule that folds the instruction to operand |foldToArg| +// (0 or 1) if operand |arg| (0 or 1) is a zero constant. +FoldingRule RedundantBinaryOpWithZeroOperand(uint32_t arg, uint32_t foldToArg) { + return [arg, foldToArg]( + IRContext* context, Instruction* inst, + const std::vector& constants) { + assert(constants.size() == 2); - uint32_t operand = std::numeric_limits::max(); - const analysis::Type* operand_type = nullptr; - if (constants[0] && constants[0]->IsZero()) { - operand = inst->GetSingleWordInOperand(1); - operand_type = constants[0]->type(); - } else if (constants[1] && constants[1]->IsZero()) { - operand = inst->GetSingleWordInOperand(0); - operand_type = constants[1]->type(); - } + if (constants[arg] && constants[arg]->IsZero()) { + auto operand = inst->GetSingleWordInOperand(foldToArg); + auto operand_type = constants[arg]->type(); - if (operand != std::numeric_limits::max()) { const analysis::Type* inst_type = context->get_type_mgr()->GetType(inst->type_id()); if (inst_type->IsSame(operand_type)) { @@ -2539,6 +2564,117 @@ FoldingRule RedundantIAdd() { }; } +// This rule handles any of RedundantBinaryRhs0Ops with a 0 or vector 0 on the +// right-hand side (a | 0 => a). +static const constexpr spv::Op RedundantBinaryRhs0Ops[] = { + spv::Op::OpBitwiseOr, + spv::Op::OpBitwiseXor, + spv::Op::OpShiftRightLogical, + spv::Op::OpShiftRightArithmetic, + spv::Op::OpShiftLeftLogical, + spv::Op::OpIAdd, + spv::Op::OpISub}; +FoldingRule RedundantBinaryRhs0(spv::Op op) { + assert(std::find(std::begin(RedundantBinaryRhs0Ops), + std::end(RedundantBinaryRhs0Ops), + op) != std::end(RedundantBinaryRhs0Ops) && + "Wrong opcode."); + (void)op; + return RedundantBinaryOpWithZeroOperand(1, 0); +} + +// This rule handles any of RedundantBinaryLhs0Ops with a 0 or vector 0 on the +// left-hand side (0 | a => a). +static const constexpr spv::Op RedundantBinaryLhs0Ops[] = { + spv::Op::OpBitwiseOr, spv::Op::OpBitwiseXor, spv::Op::OpIAdd}; +FoldingRule RedundantBinaryLhs0(spv::Op op) { + assert(std::find(std::begin(RedundantBinaryLhs0Ops), + std::end(RedundantBinaryLhs0Ops), + op) != std::end(RedundantBinaryLhs0Ops) && + "Wrong opcode."); + (void)op; + return RedundantBinaryOpWithZeroOperand(0, 1); +} + +// This rule handles shifts and divisions of 0 or vector 0 by any amount +// (0 >> a => 0). +static const constexpr spv::Op RedundantBinaryLhs0To0Ops[] = { + spv::Op::OpShiftRightLogical, + spv::Op::OpShiftRightArithmetic, + spv::Op::OpShiftLeftLogical, + spv::Op::OpSDiv, + spv::Op::OpUDiv, + spv::Op::OpSMod, + spv::Op::OpUMod}; +FoldingRule RedundantBinaryLhs0To0(spv::Op op) { + assert(std::find(std::begin(RedundantBinaryLhs0To0Ops), + std::end(RedundantBinaryLhs0To0Ops), + op) != std::end(RedundantBinaryLhs0To0Ops) && + "Wrong opcode."); + (void)op; + return RedundantBinaryOpWithZeroOperand(0, 0); +} + +// Returns true if all elements in |c| are 1. +bool IsAllInt1(const analysis::Constant* c) { + if (auto composite = c->AsCompositeConstant()) { + auto& components = composite->GetComponents(); + return std::all_of(std::begin(components), std::end(components), IsAllInt1); + } else if (c->AsIntConstant()) { + return c->GetSignExtendedValue() == 1; + } + + return false; +} + +// This rule handles divisions by 1 or vector 1 (a / 1 => a). +FoldingRule RedundantSUDiv() { + return [](IRContext* context, Instruction* inst, + const std::vector& constants) { + assert(constants.size() == 2); + assert((inst->opcode() == spv::Op::OpUDiv || + inst->opcode() == spv::Op::OpSDiv) && + "Wrong opcode."); + + if (constants[1] && IsAllInt1(constants[1])) { + auto operand = inst->GetSingleWordInOperand(0); + auto operand_type = constants[1]->type(); + + const analysis::Type* inst_type = + context->get_type_mgr()->GetType(inst->type_id()); + if (inst_type->IsSame(operand_type)) { + inst->SetOpcode(spv::Op::OpCopyObject); + } else { + inst->SetOpcode(spv::Op::OpBitcast); + } + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {operand}}}); + return true; + } + return false; + }; +} + +// This rule handles modulo from division by 1 or vector 1 (a % 1 => 0). +FoldingRule RedundantSUMod() { + return [](IRContext* context, Instruction* inst, + const std::vector& constants) { + assert(constants.size() == 2); + assert((inst->opcode() == spv::Op::OpUMod || + inst->opcode() == spv::Op::OpSMod) && + "Wrong opcode."); + + if (constants[1] && IsAllInt1(constants[1])) { + auto type = context->get_type_mgr()->GetType(inst->type_id()); + auto zero_id = context->get_constant_mgr()->GetNullConstId(type); + + inst->SetOpcode(spv::Op::OpCopyObject); + inst->SetInOperands({{SPV_OPERAND_TYPE_ID, {zero_id}}}); + return true; + } + return false; + }; +} + // This rule look for a dot with a constant vector containing a single 1 and // the rest 0s. This is the same as doing an extract. FoldingRule DotProductDoingExtract() { @@ -2876,6 +3012,17 @@ void FoldingRules::AddFoldingRules() { // Note that the order in which rules are added to the list matters. If a rule // applies to the instruction, the rest of the rules will not be attempted. // Take that into consideration. + for (auto op : RedundantBinaryRhs0Ops) + rules_[op].push_back(RedundantBinaryRhs0(op)); + for (auto op : RedundantBinaryLhs0Ops) + rules_[op].push_back(RedundantBinaryLhs0(op)); + for (auto op : RedundantBinaryLhs0To0Ops) + rules_[op].push_back(RedundantBinaryLhs0To0(op)); + rules_[spv::Op::OpSDiv].push_back(RedundantSUDiv()); + rules_[spv::Op::OpUDiv].push_back(RedundantSUDiv()); + rules_[spv::Op::OpSMod].push_back(RedundantSUMod()); + rules_[spv::Op::OpUMod].push_back(RedundantSUMod()); + rules_[spv::Op::OpBitcast].push_back(BitCastScalarOrVector()); rules_[spv::Op::OpCompositeConstruct].push_back( @@ -2907,6 +3054,8 @@ void FoldingRules::AddFoldingRules() { rules_[spv::Op::OpFDiv].push_back(MergeDivMulArithmetic()); rules_[spv::Op::OpFDiv].push_back(MergeDivNegateArithmetic()); + rules_[spv::Op::OpFMod].push_back(RedundantFMod()); + rules_[spv::Op::OpFMul].push_back(RedundantFMul()); rules_[spv::Op::OpFMul].push_back(MergeMulMulArithmetic()); rules_[spv::Op::OpFMul].push_back(MergeMulDivArithmetic()); @@ -2921,7 +3070,6 @@ void FoldingRules::AddFoldingRules() { rules_[spv::Op::OpFSub].push_back(MergeSubAddArithmetic()); rules_[spv::Op::OpFSub].push_back(MergeSubSubArithmetic()); - rules_[spv::Op::OpIAdd].push_back(RedundantIAdd()); rules_[spv::Op::OpIAdd].push_back(MergeAddNegateArithmetic()); rules_[spv::Op::OpIAdd].push_back(MergeAddAddArithmetic()); rules_[spv::Op::OpIAdd].push_back(MergeAddSubArithmetic()); diff --git a/third_party/spirv-tools/source/opt/function.h b/third_party/spirv-tools/source/opt/function.h index 8c0472cd29..45a0b23ba5 100644 --- a/third_party/spirv-tools/source/opt/function.h +++ b/third_party/spirv-tools/source/opt/function.h @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,7 @@ class Function { public: using iterator = UptrVectorIterator; using const_iterator = UptrVectorIterator; + using ParamList = std::vector>; // Creates a function instance declared by the given OpFunction instruction // |def_inst|. @@ -77,6 +79,23 @@ class Function { // Does nothing if the function doesn't have such a parameter. inline void RemoveParameter(uint32_t id); + // Rewrites the function parameters by calling a replacer callback. + // The replacer takes two parameters: an expiring unique pointer to a current + // instruction, and a back-inserter into a new list of unique pointers to + // instructions. The replacer is called for each current parameter, in order. + // Not valid to call while also iterating through the parameter list, e.g. + // within the ForEachParam method. + using RewriteParamFn = std::function&&, std::back_insert_iterator&)>; + void RewriteParams(RewriteParamFn& replacer) { + ParamList new_params; + auto appender = std::back_inserter(new_params); + for (auto& param : params_) { + replacer(std::move(param), appender); + } + params_ = std::move(new_params); + } + // Saves the given function end instruction. inline void SetFunctionEnd(std::unique_ptr end_inst); @@ -197,7 +216,7 @@ class Function { // The OpFunction instruction that begins the definition of this function. std::unique_ptr def_inst_; // All parameters to this function. - std::vector> params_; + ParamList params_; // All debug instructions in this function's header. InstructionList debug_insts_in_header_; // All basic blocks inside this function in specification order diff --git a/third_party/spirv-tools/source/opt/inline_exhaustive_pass.cpp b/third_party/spirv-tools/source/opt/inline_exhaustive_pass.cpp index bef45017f2..9cdea43d7d 100644 --- a/third_party/spirv-tools/source/opt/inline_exhaustive_pass.cpp +++ b/third_party/spirv-tools/source/opt/inline_exhaustive_pass.cpp @@ -55,6 +55,11 @@ Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) { } } } + + if (modified) { + FixDebugDeclares(func); + } + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); } diff --git a/third_party/spirv-tools/source/opt/inline_pass.cpp b/third_party/spirv-tools/source/opt/inline_pass.cpp index 318643341a..193e276ff2 100644 --- a/third_party/spirv-tools/source/opt/inline_pass.cpp +++ b/third_party/spirv-tools/source/opt/inline_pass.cpp @@ -30,6 +30,8 @@ namespace { constexpr int kSpvFunctionCallFunctionId = 2; constexpr int kSpvFunctionCallArgumentId = 3; constexpr int kSpvReturnValueId = 0; +constexpr int kSpvDebugDeclareVarInIdx = 3; +constexpr int kSpvAccessChainBaseInIdx = 0; } // namespace uint32_t InlinePass::AddPointerToType(uint32_t type_id, @@ -858,5 +860,68 @@ void InlinePass::InitializeInline() { InlinePass::InlinePass() {} +void InlinePass::FixDebugDeclares(Function* func) { + std::map access_chains; + std::vector debug_declare_insts; + + func->ForEachInst([&access_chains, &debug_declare_insts](Instruction* inst) { + if (inst->opcode() == spv::Op::OpAccessChain) { + access_chains[inst->result_id()] = inst; + } + if (inst->GetCommonDebugOpcode() == CommonDebugInfoDebugDeclare) { + debug_declare_insts.push_back(inst); + } + }); + + for (auto& inst : debug_declare_insts) { + FixDebugDeclare(inst, access_chains); + } +} + +void InlinePass::FixDebugDeclare( + Instruction* dbg_declare_inst, + const std::map& access_chains) { + do { + uint32_t var_id = + dbg_declare_inst->GetSingleWordInOperand(kSpvDebugDeclareVarInIdx); + + // The def-use chains are not kept up to date while inlining, so we need to + // get the variable by traversing the functions. + auto it = access_chains.find(var_id); + if (it == access_chains.end()) { + return; + } + Instruction* access_chain = it->second; + + // If the variable id in the debug declare is an access chain, it is + // invalid. it needs to be fixed up. The debug declare will be updated so + // that its Var operand becomes the base of the access chain. The indexes of + // the access chain are prepended before the indexes of the debug declare. + + std::vector operands; + for (int i = 0; i < kSpvDebugDeclareVarInIdx; i++) { + operands.push_back(dbg_declare_inst->GetInOperand(i)); + } + + uint32_t access_chain_base = + access_chain->GetSingleWordInOperand(kSpvAccessChainBaseInIdx); + operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {access_chain_base})); + operands.push_back( + dbg_declare_inst->GetInOperand(kSpvDebugDeclareVarInIdx + 1)); + + for (uint32_t i = kSpvAccessChainBaseInIdx + 1; + i < access_chain->NumInOperands(); ++i) { + operands.push_back(access_chain->GetInOperand(i)); + } + + for (uint32_t i = kSpvDebugDeclareVarInIdx + 2; + i < dbg_declare_inst->NumInOperands(); ++i) { + operands.push_back(dbg_declare_inst->GetInOperand(i)); + } + + dbg_declare_inst->SetInOperands(std::move(operands)); + } while (true); +} + } // namespace opt } // namespace spvtools diff --git a/third_party/spirv-tools/source/opt/inline_pass.h b/third_party/spirv-tools/source/opt/inline_pass.h index 1c9d60e32d..7bea31d1d5 100644 --- a/third_party/spirv-tools/source/opt/inline_pass.h +++ b/third_party/spirv-tools/source/opt/inline_pass.h @@ -150,6 +150,12 @@ class InlinePass : public Pass { // Initialize state for optimization of |module| void InitializeInline(); + // Fixes invalid debug declare functions in `func` that were caused by + // inlining. This function cannot be called while in the middle of inlining + // because it needs to be able to find the instructions that define an + // id. + void FixDebugDeclares(Function* func); + // Map from function's result id to function. std::unordered_map id2function_; @@ -241,6 +247,11 @@ class InlinePass : public Pass { // structural dominance. void UpdateSingleBlockLoopContinueTarget( uint32_t new_id, std::vector>* new_blocks); + + // Replaces the `var` operand of `dbg_declare_inst` and updates the indexes + // accordingly, if it is the id of an access chain in `access_chains`. + void FixDebugDeclare(Instruction* dbg_declare_inst, + const std::map& access_chains); }; } // namespace opt diff --git a/third_party/spirv-tools/source/opt/ir_builder.h b/third_party/spirv-tools/source/opt/ir_builder.h index f3e0afceae..f7ac692a90 100644 --- a/third_party/spirv-tools/source/opt/ir_builder.h +++ b/third_party/spirv-tools/source/opt/ir_builder.h @@ -15,6 +15,7 @@ #ifndef SOURCE_OPT_IR_BUILDER_H_ #define SOURCE_OPT_IR_BUILDER_H_ +#include #include #include #include @@ -480,8 +481,11 @@ class InstructionBuilder { return AddInstruction(std::move(select)); } - Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id, - std::vector ids) { + Instruction* AddOpcodeAccessChain(spv::Op opcode, uint32_t type_id, + uint32_t base_ptr_id, + const std::vector& ids) { + assert(opcode == spv::Op::OpAccessChain || + opcode == spv::Op::OpInBoundsAccessChain); std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}}); @@ -490,12 +494,22 @@ class InstructionBuilder { } // TODO(1841): Handle id overflow. - std::unique_ptr new_inst( - new Instruction(GetContext(), spv::Op::OpAccessChain, type_id, - GetContext()->TakeNextId(), operands)); + std::unique_ptr new_inst(new Instruction( + GetContext(), opcode, type_id, GetContext()->TakeNextId(), operands)); return AddInstruction(std::move(new_inst)); } + Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id, + const std::vector& ids) { + return AddOpcodeAccessChain(spv::Op::OpAccessChain, type_id, base_ptr_id, + ids); + } + Instruction* AddInBoundsAccessChain(uint32_t type_id, uint32_t base_ptr_id, + const std::vector& ids) { + return AddOpcodeAccessChain(spv::Op::OpInBoundsAccessChain, type_id, + base_ptr_id, ids); + } + Instruction* AddLoad(uint32_t type_id, uint32_t base_ptr_id, uint32_t alignment = 0) { std::vector operands; @@ -514,9 +528,19 @@ class InstructionBuilder { return AddInstruction(std::move(new_inst)); } + Instruction* AddCopyObject(uint32_t type_id, uint32_t value_id) { + std::vector operands{{SPV_OPERAND_TYPE_ID, {value_id}}}; + + // TODO(1841): Handle id overflow. + std::unique_ptr new_inst( + new Instruction(GetContext(), spv::Op::OpCopyObject, type_id, + GetContext()->TakeNextId(), operands)); + return AddInstruction(std::move(new_inst)); + } + Instruction* AddVariable(uint32_t type_id, uint32_t storage_class) { std::vector operands; - operands.push_back({SPV_OPERAND_TYPE_ID, {storage_class}}); + operands.push_back({SPV_OPERAND_TYPE_STORAGE_CLASS, {storage_class}}); std::unique_ptr new_inst( new Instruction(GetContext(), spv::Op::OpVariable, type_id, GetContext()->TakeNextId(), operands)); @@ -572,6 +596,26 @@ class InstructionBuilder { return AddInstruction(std::move(new_inst)); } + Instruction* AddDecoration(uint32_t target_id, spv::Decoration d, + const std::vector& literals) { + std::vector operands; + operands.push_back({SPV_OPERAND_TYPE_ID, {target_id}}); + operands.push_back({SPV_OPERAND_TYPE_DECORATION, {uint32_t(d)}}); + for (uint32_t literal : literals) { + operands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {literal}}); + } + + std::unique_ptr new_inst( + new Instruction(GetContext(), spv::Op::OpDecorate, 0, 0, operands)); + // Decorations are annotation instructions. Add it via the IR context, + // so the decoration manager will be updated. + // Decorations don't belong to basic blocks, so there is no need + // to update the instruction to block mapping. + Instruction* result = new_inst.get(); + GetContext()->AddAnnotationInst(std::move(new_inst)); + return result; + } + Instruction* AddNaryExtendedInstruction( uint32_t result_type, uint32_t set, uint32_t instruction, const std::vector& ext_operands) { @@ -593,6 +637,23 @@ class InstructionBuilder { return AddInstruction(std::move(new_inst)); } + Instruction* AddSampledImage(uint32_t sampled_image_type_id, + uint32_t image_id, uint32_t sampler_id) { + std::vector operands; + operands.push_back({SPV_OPERAND_TYPE_ID, {image_id}}); + operands.push_back({SPV_OPERAND_TYPE_ID, {sampler_id}}); + + uint32_t result_id = GetContext()->TakeNextId(); + if (result_id == 0) { + return nullptr; + } + + std::unique_ptr new_inst( + new Instruction(GetContext(), spv::Op::OpSampledImage, + sampled_image_type_id, result_id, operands)); + return AddInstruction(std::move(new_inst)); + } + // Inserts the new instruction before the insertion point. Instruction* AddInstruction(std::unique_ptr&& insn) { Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn)); diff --git a/third_party/spirv-tools/source/opt/ir_context.cpp b/third_party/spirv-tools/source/opt/ir_context.cpp index 6be7b05669..3f41965ea8 100644 --- a/third_party/spirv-tools/source/opt/ir_context.cpp +++ b/third_party/spirv-tools/source/opt/ir_context.cpp @@ -564,6 +564,7 @@ void IRContext::AddCombinatorsForCapability(uint32_t capability) { (uint32_t)spv::Op::OpCompositeConstruct, (uint32_t)spv::Op::OpCompositeExtract, (uint32_t)spv::Op::OpCompositeInsert, + (uint32_t)spv::Op::OpCopyLogical, (uint32_t)spv::Op::OpCopyObject, (uint32_t)spv::Op::OpTranspose, (uint32_t)spv::Op::OpSampledImage, diff --git a/third_party/spirv-tools/source/opt/optimizer.cpp b/third_party/spirv-tools/source/opt/optimizer.cpp index a41885c0e6..aedca998f5 100644 --- a/third_party/spirv-tools/source/opt/optimizer.cpp +++ b/third_party/spirv-tools/source/opt/optimizer.cpp @@ -189,7 +189,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) { .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass()) .RegisterPass(CreateAggressiveDCEPass(preserve_interface)) - .RegisterPass(CreateScalarReplacementPass()) + .RegisterPass(CreateScalarReplacementPass(0)) .RegisterPass(CreateLocalAccessChainConvertPass()) .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass()) @@ -203,7 +203,7 @@ Optimizer& Optimizer::RegisterPerformancePasses(bool preserve_interface) { .RegisterPass(CreateRedundancyEliminationPass()) .RegisterPass(CreateCombineAccessChainsPass()) .RegisterPass(CreateSimplificationPass()) - .RegisterPass(CreateScalarReplacementPass()) + .RegisterPass(CreateScalarReplacementPass(0)) .RegisterPass(CreateLocalAccessChainConvertPass()) .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass()) .RegisterPass(CreateLocalSingleStoreElimPass()) @@ -401,7 +401,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag, RegisterPass(CreateLoopUnswitchPass()); } else if (pass_name == "scalar-replacement") { if (pass_args.size() == 0) { - RegisterPass(CreateScalarReplacementPass()); + RegisterPass(CreateScalarReplacementPass(0)); } else { int limit = -1; if (pass_args.find_first_not_of("0123456789") == std::string::npos) { @@ -637,6 +637,10 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag, } } else if (pass_name == "trim-capabilities") { RegisterPass(CreateTrimCapabilitiesPass()); + } else if (pass_name == "split-combined-image-sampler") { + RegisterPass(CreateSplitCombinedImageSamplerPass()); + } else if (pass_name == "resolve-binding-conflicts") { + RegisterPass(CreateResolveBindingConflictsPass()); } else { Errorf(consumer(), nullptr, {}, "Unknown flag '--%s'. Use --help for a list of valid flags", @@ -1188,6 +1192,16 @@ Optimizer::PassToken CreateOpExtInstWithForwardReferenceFixupPass() { MakeUnique()); } +Optimizer::PassToken CreateSplitCombinedImageSamplerPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateResolveBindingConflictsPass() { + return MakeUnique( + MakeUnique()); +} + } // namespace spvtools extern "C" { diff --git a/third_party/spirv-tools/source/opt/passes.h b/third_party/spirv-tools/source/opt/passes.h index 3311529aaa..6c329787e4 100644 --- a/third_party/spirv-tools/source/opt/passes.h +++ b/third_party/spirv-tools/source/opt/passes.h @@ -74,9 +74,11 @@ #include "source/opt/remove_unused_interface_variables_pass.h" #include "source/opt/replace_desc_array_access_using_var_index.h" #include "source/opt/replace_invalid_opc.h" +#include "source/opt/resolve_binding_conflicts_pass.h" #include "source/opt/scalar_replacement_pass.h" #include "source/opt/set_spec_constant_default_value_pass.h" #include "source/opt/simplification_pass.h" +#include "source/opt/split_combined_image_sampler_pass.h" #include "source/opt/spread_volatile_semantics.h" #include "source/opt/ssa_rewrite_pass.h" #include "source/opt/strength_reduction_pass.h" diff --git a/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.cpp b/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.cpp new file mode 100644 index 0000000000..18ea2e5921 --- /dev/null +++ b/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.cpp @@ -0,0 +1,328 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/opt/resolve_binding_conflicts_pass.h" + +#include +#include +#include +#include + +#include "source/opt/decoration_manager.h" +#include "source/opt/def_use_manager.h" +#include "source/opt/instruction.h" +#include "source/opt/ir_builder.h" +#include "source/opt/ir_context.h" +#include "spirv/unified1/spirv.h" + +namespace spvtools { +namespace opt { + +// A VarBindingInfo contains the binding information for a single resource +// variable. +// +// Exactly one such object is created per resource variable in the +// module. In particular, when a resource variable is statically used by +// more than one entry point, those entry points share the same VarBindingInfo +// object for that variable. +struct VarBindingInfo { + const Instruction* const var; + const uint32_t descriptor_set; + Instruction* const binding_decoration; + + // Returns the binding number. + uint32_t binding() const { + return binding_decoration->GetSingleWordInOperand(2); + } + // Sets the binding number to 'b'. + void updateBinding(uint32_t b) { binding_decoration->SetOperand(2, {b}); } +}; + +// The bindings in the same descriptor set that are used by an entry point. +using BindingList = std::vector; +// A map from descriptor set number to the list of bindings in that descriptor +// set, as used by a particular entry point. +using DescriptorSets = std::unordered_map; + +IRContext::Analysis ResolveBindingConflictsPass::GetPreservedAnalyses() { + // All analyses are kept up to date. + // At most this modifies the Binding numbers on variables. + return IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators | + IRContext::kAnalysisCFG | IRContext::kAnalysisDominatorAnalysis | + IRContext::kAnalysisLoopAnalysis | IRContext::kAnalysisNameMap | + IRContext::kAnalysisScalarEvolution | + IRContext::kAnalysisRegisterPressure | + IRContext::kAnalysisValueNumberTable | + IRContext::kAnalysisStructuredCFG | IRContext::kAnalysisBuiltinVarId | + IRContext::kAnalysisIdToFuncMapping | IRContext::kAnalysisConstants | + IRContext::kAnalysisTypes | IRContext::kAnalysisDebugInfo | + IRContext::kAnalysisLiveness; +} + +// Orders variable binding info objects. +// * The binding number is most signficant; +// * Then a sampler-like object compares greater than non-sampler like object. +// * Otherwise compare based on variable ID. +// This provides a total order among bindings in a descriptor set for a valid +// Vulkan module. +bool Less(const VarBindingInfo* const lhs, const VarBindingInfo* const rhs) { + if (lhs->binding() < rhs->binding()) return true; + if (lhs->binding() > rhs->binding()) return false; + + // Examine types. + // In valid Vulkan the only conflict can occur between + // images and samplers. We only care about a specific + // comparison when one is a image-like thing and the other + // is a sampler-like thing of the same shape. So unwrap + // types until we hit one of those two. + + auto* def_use_mgr = lhs->var->context()->get_def_use_mgr(); + + // Returns the type found by iteratively following pointer pointee type, + // or array element type. + auto unwrap = [&def_use_mgr](Instruction* ty) { + bool keep_going = true; + do { + switch (ty->opcode()) { + case spv::Op::OpTypePointer: + ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(1)); + break; + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: + ty = def_use_mgr->GetDef(ty->GetSingleWordInOperand(0)); + break; + default: + keep_going = false; + break; + } + } while (keep_going); + return ty; + }; + + auto* lhs_ty = unwrap(def_use_mgr->GetDef(lhs->var->type_id())); + auto* rhs_ty = unwrap(def_use_mgr->GetDef(rhs->var->type_id())); + if (lhs_ty->opcode() == rhs_ty->opcode()) { + // Pick based on variable ID. + return lhs->var->result_id() < rhs->var->result_id(); + } + // A sampler is always greater than an image. + if (lhs_ty->opcode() == spv::Op::OpTypeSampler) { + return false; + } + if (rhs_ty->opcode() == spv::Op::OpTypeSampler) { + return true; + } + // Pick based on variable ID. + return lhs->var->result_id() < rhs->var->result_id(); +} + +// Summarizes the caller-callee relationships between functions in a module. +class CallGraph { + public: + // Returns the list of all functions statically reachable from entry points, + // where callees precede callers. + const std::vector& CalleesBeforeCallers() const { + return visit_order_; + } + // Returns the list functions called from a given function. + const std::unordered_set& Callees(uint32_t caller) { + return calls_[caller]; + } + + CallGraph(IRContext& context) { + // Populate calls_. + std::queue callee_queue; + for (const auto& fn : *context.module()) { + auto& callees = calls_[fn.result_id()]; + context.AddCalls(&fn, &callee_queue); + while (!callee_queue.empty()) { + callees.insert(callee_queue.front()); + callee_queue.pop(); + } + } + + // Perform depth-first search, starting from each entry point. + // Populates visit_order_. + for (const auto& ep : context.module()->entry_points()) { + Visit(ep.GetSingleWordInOperand(1)); + } + } + + private: + // Visits a function, recursively visiting its callees. Adds this ID + // to the visit_order after all callees have been visited. + void Visit(uint32_t func_id) { + if (visited_.count(func_id)) { + return; + } + visited_.insert(func_id); + for (auto callee_id : calls_[func_id]) { + Visit(callee_id); + } + visit_order_.push_back(func_id); + } + + // Maps the ID of a function to the IDs of functions it calls. + std::unordered_map> calls_; + + // IDs of visited functions; + std::unordered_set visited_; + // IDs of functions, where callees precede callers. + std::vector visit_order_; +}; + +// Returns vector binding info for all resource variables in the module. +auto GetVarBindings(IRContext& context) { + std::vector vars; + auto* deco_mgr = context.get_decoration_mgr(); + for (auto& inst : context.module()->types_values()) { + if (inst.opcode() == spv::Op::OpVariable) { + Instruction* descriptor_set_deco = nullptr; + Instruction* binding_deco = nullptr; + for (auto* deco : deco_mgr->GetDecorationsFor(inst.result_id(), false)) { + switch (static_cast(deco->GetSingleWordInOperand(1))) { + case spv::Decoration::DescriptorSet: + assert(!descriptor_set_deco); + descriptor_set_deco = deco; + break; + case spv::Decoration::Binding: + assert(!binding_deco); + binding_deco = deco; + break; + default: + break; + } + } + if (descriptor_set_deco && binding_deco) { + vars.push_back({&inst, descriptor_set_deco->GetSingleWordInOperand(2), + binding_deco}); + } + } + } + return vars; +} + +// Merges the bindings from source into sink. Maintains order and uniqueness +// within a list of bindings. +void Merge(DescriptorSets& sink, const DescriptorSets& source) { + for (auto index_and_bindings : source) { + const uint32_t index = index_and_bindings.first; + const BindingList& src1 = index_and_bindings.second; + const BindingList& src2 = sink[index]; + BindingList merged; + merged.resize(src1.size() + src2.size()); + auto merged_end = std::merge(src1.begin(), src1.end(), src2.begin(), + src2.end(), merged.begin(), Less); + auto unique_end = std::unique(merged.begin(), merged_end); + merged.resize(unique_end - merged.begin()); + sink[index] = std::move(merged); + } +} + +// Resolves conflicts within this binding list, so the binding number on an +// item is at least one more than the binding number on the previous item. +// When this does not yet hold, increase the binding number on the second +// item in the pair. Returns true if any changes were applied. +bool ResolveConflicts(BindingList& bl) { + bool changed = false; + for (size_t i = 1; i < bl.size(); i++) { + const auto prev_num = bl[i - 1]->binding(); + if (prev_num >= bl[i]->binding()) { + bl[i]->updateBinding(prev_num + 1); + changed = true; + } + } + return changed; +} + +Pass::Status ResolveBindingConflictsPass::Process() { + // Assumes the descriptor set and binding decorations are not provided + // via decoration groups. Decoration groups were deprecated in SPIR-V 1.3 + // Revision 6. I have not seen any compiler generate them. --dneto + + auto vars = GetVarBindings(*context()); + + // Maps a function ID to the variables used directly or indirectly by the + // function, organized into descriptor sets. Each descriptor set + // consists of a BindingList of distinct variables. + std::unordered_map used_vars; + + // Determine variables directly used by functions. + auto* def_use_mgr = context()->get_def_use_mgr(); + for (auto& var : vars) { + std::unordered_set visited_functions_for_var; + def_use_mgr->ForEachUser(var.var, [&](Instruction* user) { + if (auto* block = context()->get_instr_block(user)) { + auto* fn = block->GetParent(); + assert(fn); + const auto fn_id = fn->result_id(); + if (visited_functions_for_var.insert(fn_id).second) { + used_vars[fn_id][var.descriptor_set].push_back(&var); + } + } + }); + } + + // Sort within a descriptor set by binding number. + for (auto& sets_for_fn : used_vars) { + for (auto& ds : sets_for_fn.second) { + BindingList& bindings = ds.second; + std::stable_sort(bindings.begin(), bindings.end(), Less); + } + } + + // Propagate from callees to callers. + CallGraph call_graph(*context()); + for (const uint32_t caller : call_graph.CalleesBeforeCallers()) { + DescriptorSets& caller_ds = used_vars[caller]; + for (const uint32_t callee : call_graph.Callees(caller)) { + Merge(caller_ds, used_vars[callee]); + } + } + + // At this point, the descriptor sets associated with each entry point + // capture exactly the set of resource variables statically used + // by the static call tree of that entry point. + + // Resolve conflicts. + // VarBindingInfo objects may be shared between the bindings lists. + // Updating a binding in one list can require updating another list later. + // So repeat updates until settling. + + // The union of BindingLists across all entry points. + std::vector ep_bindings; + + for (auto& ep : context()->module()->entry_points()) { + for (auto& ds : used_vars[ep.GetSingleWordInOperand(1)]) { + BindingList& bindings = ds.second; + ep_bindings.push_back(&bindings); + } + } + bool modified = false; + bool found_conflict; + do { + found_conflict = false; + for (BindingList* bl : ep_bindings) { + found_conflict |= ResolveConflicts(*bl); + } + modified |= found_conflict; + } while (found_conflict); + + return modified ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; +} + +} // namespace opt +} // namespace spvtools diff --git a/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.h b/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.h new file mode 100644 index 0000000000..d1ab433d86 --- /dev/null +++ b/third_party/spirv-tools/source/opt/resolve_binding_conflicts_pass.h @@ -0,0 +1,38 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_ +#define LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_ + +#include +#include +#include + +#include "source/diagnostic.h" +#include "source/opt/pass.h" +#include "source/util/small_vector.h" + +namespace spvtools { +namespace opt { +class ResolveBindingConflictsPass : public Pass { + public: + virtual ~ResolveBindingConflictsPass() override = default; + const char* name() const override { return "resolve-binding-conflicts"; } + IRContext::Analysis GetPreservedAnalyses() override; + Status Process() override; +}; +} // namespace opt +} // namespace spvtools + +#endif // LIBSPIRV_OPT_RESOLVE_BINDING_CONFLICTS_PASS_H_ diff --git a/third_party/spirv-tools/source/opt/scalar_replacement_pass.h b/third_party/spirv-tools/source/opt/scalar_replacement_pass.h index c73ecfd98b..61d341e5cf 100644 --- a/third_party/spirv-tools/source/opt/scalar_replacement_pass.h +++ b/third_party/spirv-tools/source/opt/scalar_replacement_pass.h @@ -33,7 +33,7 @@ namespace opt { // Documented in optimizer.hpp class ScalarReplacementPass : public MemPass { private: - static constexpr uint32_t kDefaultLimit = 100; + static constexpr uint32_t kDefaultLimit = 0; public: ScalarReplacementPass(uint32_t limit = kDefaultLimit) diff --git a/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.cpp b/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.cpp new file mode 100644 index 0000000000..e454e3b96b --- /dev/null +++ b/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.cpp @@ -0,0 +1,641 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "source/opt/split_combined_image_sampler_pass.h" + +#include +#include +#include + +#include "source/opt/instruction.h" +#include "source/opt/ir_builder.h" +#include "source/opt/ir_context.h" +#include "source/opt/type_manager.h" +#include "source/opt/types.h" +#include "source/util/make_unique.h" +#include "source/util/string_utils.h" +#include "spirv/unified1/spirv.h" + +namespace spvtools { +namespace opt { + +#define CHECK(cond) \ + { \ + if ((cond) != SPV_SUCCESS) return Pass::Status::Failure; \ + } + +#define CHECK_STATUS(cond) \ + { \ + if (auto c = (cond); c != SPV_SUCCESS) return c; \ + } + +IRContext::Analysis SplitCombinedImageSamplerPass::GetPreservedAnalyses() { + return + // def use manager is updated + IRContext::kAnalysisDefUse + + // decorations are updated + | IRContext::kAnalysisDecorations + + // control flow is not changed + | IRContext::kAnalysisCFG // + | IRContext::kAnalysisLoopAnalysis // + | IRContext::kAnalysisStructuredCFG + + // type manager is updated + | IRContext::kAnalysisTypes; +} + +Pass::Status SplitCombinedImageSamplerPass::Process() { + def_use_mgr_ = context()->get_def_use_mgr(); + type_mgr_ = context()->get_type_mgr(); + + FindCombinedTextureSamplers(); + if (combined_types_to_remove_.empty() && !sampled_image_used_as_param_) { + return Ok(); + } + + CHECK(RemapFunctions()); + CHECK(RemapVars()); + CHECK(RemoveDeadTypes()); + + def_use_mgr_ = nullptr; + type_mgr_ = nullptr; + + return Ok(); +} + +spvtools::DiagnosticStream SplitCombinedImageSamplerPass::Fail() { + return std::move( + spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY) + << "split-combined-image-sampler: "); +} + +void SplitCombinedImageSamplerPass::FindCombinedTextureSamplers() { + for (auto& inst : context()->types_values()) { + RegisterGlobal(inst.result_id()); + switch (inst.opcode()) { + case spv::Op::OpTypeSampler: + // Modules can't have duplicate sampler types. + assert(!sampler_type_); + sampler_type_ = &inst; + break; + + case spv::Op::OpTypeSampledImage: + if (!first_sampled_image_type_) { + first_sampled_image_type_ = &inst; + } + combined_types_.insert(inst.result_id()); + def_use_mgr_->WhileEachUser(inst.result_id(), [&](Instruction* i) { + sampled_image_used_as_param_ |= + i->opcode() == spv::Op::OpTypeFunction; + return !sampled_image_used_as_param_; + }); + break; + + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: { + auto pointee_id = inst.GetSingleWordInOperand(0); + if (combined_types_.find(pointee_id) != combined_types_.end()) { + combined_types_.insert(inst.result_id()); + combined_types_to_remove_.push_back(inst.result_id()); + } + } break; + + case spv::Op::OpTypePointer: { + auto sc = + static_cast(inst.GetSingleWordInOperand(0)); + if (sc == spv::StorageClass::UniformConstant) { + auto pointee_id = inst.GetSingleWordInOperand(1); + if (combined_types_.find(pointee_id) != combined_types_.end()) { + combined_types_.insert(inst.result_id()); + combined_types_to_remove_.push_back(inst.result_id()); + } + } + } break; + + case spv::Op::OpVariable: + if (combined_types_.find(inst.type_id()) != combined_types_.end()) { + ordered_vars_.push_back(&inst); + } + break; + + default: + break; + } + } +} + +Instruction* SplitCombinedImageSamplerPass::GetSamplerType() { + if (!sampler_type_) { + analysis::Sampler s; + uint32_t sampler_type_id = type_mgr_->GetTypeInstruction(&s); + sampler_type_ = def_use_mgr_->GetDef(sampler_type_id); + assert(first_sampled_image_type_); + sampler_type_->InsertBefore(first_sampled_image_type_); + RegisterNewGlobal(sampler_type_->result_id()); + } + return sampler_type_; +} + +spv_result_t SplitCombinedImageSamplerPass::RemapVars() { + for (Instruction* var : ordered_vars_) { + CHECK_STATUS(RemapVar(var)); + } + return SPV_SUCCESS; +} + +std::pair SplitCombinedImageSamplerPass::SplitType( + Instruction& combined_kind_type) { + if (auto where = type_remap_.find(combined_kind_type.result_id()); + where != type_remap_.end()) { + auto& type_remap = where->second; + return {type_remap.image_kind_type, type_remap.sampler_kind_type}; + } + + switch (combined_kind_type.opcode()) { + case spv::Op::OpTypeSampledImage: { + auto* image_type = + def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(0)); + auto* sampler_type = GetSamplerType(); + type_remap_[combined_kind_type.result_id()] = {&combined_kind_type, + image_type, sampler_type}; + return {image_type, sampler_type}; + break; + } + case spv::Op::OpTypePointer: { + auto sc = static_cast( + combined_kind_type.GetSingleWordInOperand(0)); + if (sc == spv::StorageClass::UniformConstant) { + auto* pointee = + def_use_mgr_->GetDef(combined_kind_type.GetSingleWordInOperand(1)); + auto [image_pointee, sampler_pointee] = SplitType(*pointee); + // These would be null if the pointee is an image type or a sampler + // type. Don't decompose them. Currently this method does not check the + // assumption that it is being only called on combined types. So code + // this defensively. + if (image_pointee && sampler_pointee) { + auto* ptr_image = MakeUniformConstantPointer(image_pointee); + auto* ptr_sampler = MakeUniformConstantPointer(sampler_pointee); + type_remap_[combined_kind_type.result_id()] = { + &combined_kind_type, ptr_image, ptr_sampler}; + return {ptr_image, ptr_sampler}; + } + } + break; + } + case spv::Op::OpTypeArray: { + const auto* array_ty = + type_mgr_->GetType(combined_kind_type.result_id())->AsArray(); + assert(array_ty); + const auto* sampled_image_ty = array_ty->element_type()->AsSampledImage(); + assert(sampled_image_ty); + + const analysis::Type* image_ty = sampled_image_ty->image_type(); + assert(image_ty); + analysis::Array array_image_ty(image_ty, array_ty->length_info()); + const uint32_t array_image_ty_id = + type_mgr_->GetTypeInstruction(&array_image_ty); + auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id); + if (!IsKnownGlobal(array_image_ty_id)) { + array_image_ty_inst->InsertBefore(&combined_kind_type); + RegisterNewGlobal(array_image_ty_id); + // GetTypeInstruction also updated the def-use manager. + } + + analysis::Array sampler_array_ty( + type_mgr_->GetType(GetSamplerType()->result_id()), + array_ty->length_info()); + const uint32_t array_sampler_ty_id = + type_mgr_->GetTypeInstruction(&sampler_array_ty); + auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id); + if (!IsKnownGlobal(array_sampler_ty_id)) { + array_sampler_ty_inst->InsertBefore(&combined_kind_type); + RegisterNewGlobal(array_sampler_ty_id); + // GetTypeInstruction also updated the def-use manager. + } + return {array_image_ty_inst, array_sampler_ty_inst}; + } + case spv::Op::OpTypeRuntimeArray: { + // This is like the sized-array case, but there is no length parameter. + auto* array_ty = + type_mgr_->GetType(combined_kind_type.result_id())->AsRuntimeArray(); + assert(array_ty); + auto* sampled_image_ty = array_ty->element_type()->AsSampledImage(); + assert(sampled_image_ty); + + const analysis::Type* image_ty = sampled_image_ty->image_type(); + assert(image_ty); + analysis::RuntimeArray array_image_ty(image_ty); + const uint32_t array_image_ty_id = + type_mgr_->GetTypeInstruction(&array_image_ty); + auto* array_image_ty_inst = def_use_mgr_->GetDef(array_image_ty_id); + if (!IsKnownGlobal(array_image_ty_id)) { + array_image_ty_inst->InsertBefore(&combined_kind_type); + RegisterNewGlobal(array_image_ty_id); + // GetTypeInstruction also updated the def-use manager. + } + + analysis::RuntimeArray sampler_array_ty( + type_mgr_->GetType(GetSamplerType()->result_id())); + const uint32_t array_sampler_ty_id = + type_mgr_->GetTypeInstruction(&sampler_array_ty); + auto* array_sampler_ty_inst = def_use_mgr_->GetDef(array_sampler_ty_id); + if (!IsKnownGlobal(array_sampler_ty_id)) { + array_sampler_ty_inst->InsertBefore(&combined_kind_type); + RegisterNewGlobal(array_sampler_ty_id); + // GetTypeInstruction also updated the def-use manager. + } + return {array_image_ty_inst, array_sampler_ty_inst}; + } + default: + break; + } + return {nullptr, nullptr}; +} + +spv_result_t SplitCombinedImageSamplerPass::RemapVar( + Instruction* combined_var) { + InstructionBuilder builder(context(), combined_var, + IRContext::kAnalysisDefUse); + + // Create an image variable, and a sampler variable. + auto* combined_var_type = def_use_mgr_->GetDef(combined_var->type_id()); + auto [ptr_image_ty, ptr_sampler_ty] = SplitType(*combined_var_type); + assert(ptr_image_ty); + assert(ptr_sampler_ty); + Instruction* sampler_var = builder.AddVariable( + ptr_sampler_ty->result_id(), SpvStorageClassUniformConstant); + Instruction* image_var = builder.AddVariable(ptr_image_ty->result_id(), + SpvStorageClassUniformConstant); + modified_ = true; + return RemapUses(combined_var, image_var, sampler_var); +} + +spv_result_t SplitCombinedImageSamplerPass::RemapUses( + Instruction* combined, Instruction* image_part, Instruction* sampler_part) { + // The instructions to delete. + std::unordered_set dead_insts; + // The insertion point should be updated before using this builder. + // We needed *something* here. + InstructionBuilder builder(context(), combined, IRContext::kAnalysisDefUse); + + // This code must maintain the SPIR-V "Data rule" about sampled image values: + // > All OpSampledImage instructions, or instructions that load an image or + // > sampler reference, must be in the same block in which their Result + // > are consumed. + // + // When the code below inserts OpSampledImage instructions, it is always + // either: + // - in the same block as the previous OpSampledImage instruction it is + // replacing, or + // - in the same block as the instruction using sampled image value it is + // replacing. + // + // Assuming that rule is already honoured by the module, these updates will + // continue to honour the rule. + + // Represents a single use of a value to be remapped. + struct RemapUse { + uint32_t used_id; // The ID that is being used. + Instruction* user; + uint32_t index; + Instruction* image_part; // The image part of the replacement. + Instruction* sampler_part; // The sampler part of the replacement. + }; + // The work list of uses to be remapped. + std::vector uses; + + // Adds remap records for each use of a value to be remapped. + // Also schedules the original value for deletion. + auto add_remap = [this, &dead_insts, &uses](Instruction* combined_arg, + Instruction* image_part_arg, + Instruction* sampler_part_arg) { + const uint32_t used_combined_id = combined_arg->result_id(); + + def_use_mgr_->ForEachUse( + combined_arg, [&](Instruction* user, uint32_t use_index) { + uses.push_back({used_combined_id, user, use_index, image_part_arg, + sampler_part_arg}); + }); + dead_insts.insert(combined_arg); + }; + + add_remap(combined, image_part, sampler_part); + + // Use index-based iteration because we can add to the work list as we go + // along, and reallocation would invalidate ordinary iterators. + for (size_t use_index = 0; use_index < uses.size(); ++use_index) { + auto& use = uses[use_index]; + switch (use.user->opcode()) { + case spv::Op::OpCopyObject: { + // Append the uses of this OpCopyObject to the work list. + add_remap(use.user, image_part, sampler_part); + break; + } + case spv::Op::OpLoad: { + assert(use.index == 2 && "variable used as non-pointer index on load"); + Instruction* load = use.user; + + // Assume the loaded value is a sampled image. + assert(def_use_mgr_->GetDef(load->type_id())->opcode() == + spv::Op::OpTypeSampledImage); + + // Create loads for the image part and sampler part. + builder.SetInsertPoint(load); + auto* image = builder.AddLoad(PointeeTypeId(use.image_part), + use.image_part->result_id()); + auto* sampler = builder.AddLoad(PointeeTypeId(use.sampler_part), + use.sampler_part->result_id()); + // Create a sampled image from the loads of the two parts. + auto* sampled_image = builder.AddSampledImage( + load->type_id(), image->result_id(), sampler->result_id()); + // Replace the original sampled image value with the new one. + std::unordered_set users; + def_use_mgr_->ForEachUse( + load, [&users, sampled_image](Instruction* user, uint32_t index) { + user->SetOperand(index, {sampled_image->result_id()}); + users.insert(user); + }); + for (auto* user : users) { + def_use_mgr_->AnalyzeInstUse(user); + } + dead_insts.insert(load); + break; + } + case spv::Op::OpDecorate: { + assert(use.index == 0 && "variable used as non-target index"); + builder.SetInsertPoint(use.user); + spv::Decoration deco{use.user->GetSingleWordInOperand(1)}; + std::vector literals; + for (uint32_t i = 2; i < use.user->NumInOperands(); i++) { + literals.push_back(use.user->GetSingleWordInOperand(i)); + } + builder.AddDecoration(use.image_part->result_id(), deco, literals); + builder.AddDecoration(use.sampler_part->result_id(), deco, literals); + // KillInst will delete names and decorations, so don't schedule a + // deletion of this instruction. + break; + } + case spv::Op::OpEntryPoint: { + // The entry point lists variables in the shader interface, i.e. + // module-scope variables referenced by the static call tree rooted + // at the entry point. (It can be a proper superset). Before SPIR-V + // 1.4, only Input and Output variables are listed; in 1.4 and later, + // module-scope variables in all storage classes are listed. + // If a combined image+sampler is listed by the entry point, then + // the separated image and sampler variables should be. + assert(use.index >= 3 && + "variable used in OpEntryPoint but not as an interface ID"); + use.user->SetOperand(use.index, {use.image_part->result_id()}); + use.user->InsertOperand( + use.user->NumOperands(), + {SPV_OPERAND_TYPE_ID, {use.sampler_part->result_id()}}); + def_use_mgr_->AnalyzeInstUse(use.user); + break; + } + case spv::Op::OpName: { + // Synthesize new names from the old. + const auto name = use.user->GetOperand(1).AsString(); + AddOpName(use.image_part->result_id(), name + "_image"); + AddOpName(use.sampler_part->result_id(), name + "_sampler"); + + // KillInst will delete names and decorations, so don't schedule a + // deletion of this instruction. + break; + } + case spv::Op::OpFunctionCall: { + // Replace each combined arg with two args: the image part, then the + // sampler part. + // The combined value could have been used twice in the argument list. + // Moving things around now will invalidate the 'use' list above. + // So don't trust the use index value. + auto& call = *use.user; + // The insert API only takes absolute arg IDs, not "in" arg IDs. + const auto first_arg_operand_index = 3; // Skip the callee ID + for (uint32_t i = first_arg_operand_index; i < call.NumOperands(); + ++i) { + if (use.used_id == call.GetSingleWordOperand(i)) { + call.SetOperand(i, {use.sampler_part->result_id()}); + call.InsertOperand( + i, {SPV_OPERAND_TYPE_ID, {use.image_part->result_id()}}); + ++i; + } + } + def_use_mgr_->AnalyzeInstUse(&call); + break; + } + case spv::Op::OpAccessChain: + case spv::Op::OpInBoundsAccessChain: { + auto* original_access_chain = use.user; + builder.SetInsertPoint(original_access_chain); + // It can only be the base pointer + assert(use.index == 2); + + // Replace the original access chain with access chains for the image + // part and the sampler part. + std::vector indices; + for (uint32_t i = 3; i < original_access_chain->NumOperands(); i++) { + indices.push_back(original_access_chain->GetSingleWordOperand(i)); + } + + auto [result_image_part_ty, result_sampler_part_ty] = + SplitType(*def_use_mgr_->GetDef(original_access_chain->type_id())); + auto* result_image_part = builder.AddOpcodeAccessChain( + use.user->opcode(), result_image_part_ty->result_id(), + use.image_part->result_id(), indices); + auto* result_sampler_part = builder.AddOpcodeAccessChain( + use.user->opcode(), result_sampler_part_ty->result_id(), + use.sampler_part->result_id(), indices); + + // Remap uses of the original access chain. + add_remap(original_access_chain, result_image_part, + result_sampler_part); + break; + } + default: { + uint32_t used_type_id = def_use_mgr_->GetDef(use.used_id)->type_id(); + auto* used_type = def_use_mgr_->GetDef(used_type_id); + if (used_type->opcode() == spv::Op::OpTypeSampledImage) { + // This value being used is a sampled image value. But it's + // being replaced, so recreate it here. + // Example: used by OpImage, OpImageSampleExplicitLod, etc. + builder.SetInsertPoint(use.user); + auto* sampled_image = + builder.AddSampledImage(used_type_id, use.image_part->result_id(), + use.sampler_part->result_id()); + use.user->SetOperand(use.index, {sampled_image->result_id()}); + def_use_mgr_->AnalyzeInstUse(use.user); + break; + } + return Fail() << "unhandled user: " << *use.user; + } + } + } + + for (auto* inst : dead_insts) { + KillInst(inst); + } + + return SPV_SUCCESS; +} + +spv_result_t SplitCombinedImageSamplerPass::RemapFunctions() { + // Remap function types. A combined type can appear as a parameter, but not as + // the return type. + { + std::unordered_set dead_insts; + for (auto& inst : context()->types_values()) { + if (inst.opcode() != spv::Op::OpTypeFunction) { + continue; + } + analysis::Function* f_ty = + type_mgr_->GetType(inst.result_id())->AsFunction(); + std::vector new_params; + for (const auto* param_ty : f_ty->param_types()) { + const auto param_ty_id = type_mgr_->GetId(param_ty); + if (combined_types_.find(param_ty_id) != combined_types_.end()) { + auto* param_type = def_use_mgr_->GetDef(param_ty_id); + auto [image_type, sampler_type] = SplitType(*param_type); + assert(image_type); + assert(sampler_type); + // The image and sampler types must already exist, so there is no + // need to move them to the right spot. + new_params.push_back(type_mgr_->GetType(image_type->result_id())); + new_params.push_back(type_mgr_->GetType(sampler_type->result_id())); + } else { + new_params.push_back(param_ty); + } + } + if (new_params.size() != f_ty->param_types().size()) { + // Replace this type. + analysis::Function new_f_ty(f_ty->return_type(), new_params); + const uint32_t new_f_ty_id = type_mgr_->GetTypeInstruction(&new_f_ty); + std::unordered_set users; + def_use_mgr_->ForEachUse( + &inst, + [&users, new_f_ty_id](Instruction* user, uint32_t use_index) { + user->SetOperand(use_index, {new_f_ty_id}); + users.insert(user); + }); + for (auto* user : users) { + def_use_mgr_->AnalyzeInstUse(user); + } + dead_insts.insert(&inst); + } + } + for (auto* inst : dead_insts) { + KillInst(inst); + } + } + + // Rewite OpFunctionParameter in function definitions. + for (Function& fn : *context()->module()) { + // Rewrite the function parameters and record their replacements. + struct Replacement { + Instruction* combined; + Instruction* image; + Instruction* sampler; + }; + std::vector replacements; + + Function::RewriteParamFn rewriter = + [&](std::unique_ptr&& param, + std::back_insert_iterator& appender) { + if (combined_types_.count(param->type_id()) == 0) { + appender = std::move(param); + return; + } + + // Replace this parameter with two new parameters. + auto* combined_inst = param.release(); + auto* combined_type = def_use_mgr_->GetDef(combined_inst->type_id()); + auto [image_type, sampler_type] = SplitType(*combined_type); + auto image_param = MakeUnique( + context(), spv::Op::OpFunctionParameter, image_type->result_id(), + context()->TakeNextId(), Instruction::OperandList{}); + auto sampler_param = MakeUnique( + context(), spv::Op::OpFunctionParameter, + sampler_type->result_id(), context()->TakeNextId(), + Instruction::OperandList{}); + replacements.push_back( + {combined_inst, image_param.get(), sampler_param.get()}); + appender = std::move(image_param); + appender = std::move(sampler_param); + }; + fn.RewriteParams(rewriter); + + for (auto& r : replacements) { + modified_ = true; + def_use_mgr_->AnalyzeInstDefUse(r.image); + def_use_mgr_->AnalyzeInstDefUse(r.sampler); + CHECK_STATUS(RemapUses(r.combined, r.image, r.sampler)); + } + } + return SPV_SUCCESS; +} + +Instruction* SplitCombinedImageSamplerPass::MakeUniformConstantPointer( + Instruction* pointee) { + uint32_t ptr_id = type_mgr_->FindPointerToType( + pointee->result_id(), spv::StorageClass::UniformConstant); + auto* ptr = def_use_mgr_->GetDef(ptr_id); + if (!IsKnownGlobal(ptr_id)) { + // The pointer type was created at the end. Put it right after the + // pointee. + ptr->InsertBefore(pointee); + pointee->InsertBefore(ptr); + RegisterNewGlobal(ptr_id); + // FindPointerToType also updated the def-use manager. + } + return ptr; +} + +void SplitCombinedImageSamplerPass::AddOpName(uint32_t id, + const std::string& name) { + std::unique_ptr opname{new Instruction{ + context(), + spv::Op::OpName, + 0u, + 0u, + {{SPV_OPERAND_TYPE_ID, {id}}, + {SPV_OPERAND_TYPE_LITERAL_STRING, + utils::MakeVector(name)}}}}; + + context()->AddDebug2Inst(std::move(opname)); +} + +spv_result_t SplitCombinedImageSamplerPass::RemoveDeadTypes() { + for (auto dead_type_id : combined_types_to_remove_) { + if (auto* ty = def_use_mgr_->GetDef(dead_type_id)) { + KillInst(ty); + } + } + return SPV_SUCCESS; +} + +void SplitCombinedImageSamplerPass::KillInst(Instruction* inst) { + // IRContext::KillInst will remove associated debug instructions and + // decorations. It will delete the object only if it is already in a list. + const bool was_in_list = inst->IsInAList(); + context()->KillInst(inst); + if (!was_in_list) { + // Avoid leaking + delete inst; + } + modified_ = true; +} + +} // namespace opt +} // namespace spvtools diff --git a/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.h b/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.h new file mode 100644 index 0000000000..253f34ab7e --- /dev/null +++ b/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.h @@ -0,0 +1,169 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_ +#define LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_ + +#include +#include +#include + +#include "source/diagnostic.h" +#include "source/opt/decoration_manager.h" +#include "source/opt/def_use_manager.h" +#include "source/opt/pass.h" +#include "source/opt/type_manager.h" + +namespace spvtools { +namespace opt { + +// Replaces each combined-image sampler variable with an image variable +// and a sampler variable. Similar for function parameters. +// +// Copy the descriptor set and binding number. Vulkan allows this, surprisingly. +class SplitCombinedImageSamplerPass : public Pass { + public: + virtual ~SplitCombinedImageSamplerPass() override = default; + const char* name() const override { return "split-combined-image-sampler"; } + IRContext::Analysis GetPreservedAnalyses() override; + Status Process() override; + + private: + // Records failure for the current module, and returns a stream + // that can be used to provide user error information to the message + // consumer. + spvtools::DiagnosticStream Fail(); + + // Find variables that contain combined texture-samplers, or arrays of them. + // Also populate known_globals_. + void FindCombinedTextureSamplers(); + + // Returns the sampler type. If it does not yet exist, then it is created + // and placed before the first sampled image type. + Instruction* GetSamplerType(); + + // Remaps function types and function declarations. Each + // pointer-to-sampled-image-type operand is replaced with a pair of + // pointer-to-image-type and pointer-to-sampler-type pair. + // Updates the def-use manager and type manager. + spv_result_t RemapFunctions(); + // Remap resource variables. + // Updates the def-use manager. + spv_result_t RemapVars(); + // Remap a single resource variable for combined var. + // Updates the def-use manager and the decorations manager. + spv_result_t RemapVar(Instruction* combined_var); + // Transitively remaps uses of the combined object with uses of the + // decomposed image and sampler parts. The combined object can be sampled + // image value, a pointer to one, an array of one, or a pointer to an array + // of one. The image and sampler parts have corresponding shapes. + // Updates the def-use manager and the decorations manager. + spv_result_t RemapUses(Instruction* combined, Instruction* image_part, + Instruction* sampler_part); + // Removes types that are no longer referenced. + spv_result_t RemoveDeadTypes(); + + // Returns the type instruction for a UniformConstant pointer to the given + // pointee type. If it does not yet exist, the new type instruction is created + // and placed immediately after the pointee type instruction. Updates def-use + // and type managers, and the set of known globals. + Instruction* MakeUniformConstantPointer(Instruction* pointee); + + // Returns the ID of the pointee type for a pointer value instruction. + uint32_t PointeeTypeId(Instruction* ptr_value) { + auto* ptr_ty = def_use_mgr_->GetDef(ptr_value->type_id()); + assert(ptr_ty->opcode() == spv::Op::OpTypePointer); + return ptr_ty->GetSingleWordInOperand(1); + } + + // Creates a new OpName instruction mapping the given name to the given + // string, and adds it to the module at the end of the OpName and OpMemberName + // section. + void AddOpName(uint32_t id, const std::string& name); + + // Cached from the IRContext. Valid while Process() is running. + analysis::DefUseManager* def_use_mgr_ = nullptr; + // Cached from the IRContext. Valid while Process() is running. + analysis::TypeManager* type_mgr_ = nullptr; + + // Did processing modify the module? + bool modified_ = false; + Pass::Status Ok() { + return modified_ ? Pass::Status::SuccessWithChange + : Pass::Status::SuccessWithoutChange; + } + + // The first OpTypeSampledImage instruction in the module, if one exists. + Instruction* first_sampled_image_type_ = nullptr; + // An OpTypeSampler instruction, if one existed already, or if we created one. + Instruction* sampler_type_ = nullptr; + + // The known types and module-scope values. + // We use this to know when a new such value was created. + std::unordered_set known_globals_; + bool IsKnownGlobal(uint32_t id) const { + return known_globals_.find(id) != known_globals_.end(); + } + void RegisterGlobal(uint32_t id) { known_globals_.insert(id); } + void RegisterNewGlobal(uint32_t id) { + modified_ = true; + RegisterGlobal(id); + } + + // Deletes an instruction and associated debug and decoration instructions. + // Updates the def-use manager. + void KillInst(Instruction* inst); + + // Combined types. The known combined sampled-image type, + // and recursively pointers or arrays of them. + std::unordered_set combined_types_; + // The pre-existing types this pass should remove: pointer to + // combined type, array of combined type, pointer to array of combined type. + std::vector combined_types_to_remove_; + // Is an OpTypeSampledImage used as a function parameter? Those should be + // transformed. + bool sampled_image_used_as_param_ = false; + + // Remaps a combined-kind type to corresponding sampler-kind and image-kind + // of type. + struct TypeRemapInfo { + // The instruction for the combined type, pointer to combined type, + // or point to array of combined type. + Instruction* combined_kind_type; + // The corresponding image type, with the same shape of indirection as the + // combined_kind_type. + Instruction* image_kind_type; + // The corresponding sampler type, with the same shape of indirection as the + // combined_kind_type. + Instruction* sampler_kind_type; + }; + // Maps the ID of a combined-image-sampler type kind to its corresponding + // split parts. + std::unordered_map type_remap_; + + // Returns the image-like and sampler-like types of the same indirection shape + // as the given combined-like type. If combined_kind_type is not a combined + // type or a pointer to one, or an array of one or a pointer to an array of + // one, then returns a pair of null pointer. Either both components are + // non-null, or both components are null. Updates the def-use manager and the + // type manager if new instructions are created. + std::pair SplitType( + Instruction& combined_kind_type); + + // The combined-image-sampler variables to be replaced. + std::vector ordered_vars_; +}; +} // namespace opt +} // namespace spvtools +#endif // LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_ diff --git a/third_party/spirv-tools/source/opt/trim_capabilities_pass.cpp b/third_party/spirv-tools/source/opt/trim_capabilities_pass.cpp index 34fbc4496b..9c46996cb1 100644 --- a/third_party/spirv-tools/source/opt/trim_capabilities_pass.cpp +++ b/third_party/spirv-tools/source/opt/trim_capabilities_pass.cpp @@ -241,6 +241,37 @@ Handler_OpTypePointer_StorageUniformBufferBlock16( : std::nullopt; } +static std::optional +Handler_OpTypePointer_StorageBuffer16BitAccess(const Instruction* instruction) { + assert(instruction->opcode() == spv::Op::OpTypePointer && + "This handler only support OpTypePointer opcodes."); + + // Requires StorageBuffer, ShaderRecordBufferKHR or PhysicalStorageBuffer + // storage classes. + spv::StorageClass storage_class = spv::StorageClass( + instruction->GetSingleWordInOperand(kOpTypePointerStorageClassIndex)); + if (storage_class != spv::StorageClass::StorageBuffer && + storage_class != spv::StorageClass::ShaderRecordBufferKHR && + storage_class != spv::StorageClass::PhysicalStorageBuffer) { + return std::nullopt; + } + + const auto* decoration_mgr = instruction->context()->get_decoration_mgr(); + const bool matchesCondition = + AnyTypeOf(instruction, [decoration_mgr](const Instruction* item) { + if (!decoration_mgr->HasDecoration(item->result_id(), + spv::Decoration::Block)) { + return false; + } + + return AnyTypeOf(item, is16bitType); + }); + + return matchesCondition + ? std::optional(spv::Capability::StorageBuffer16BitAccess) + : std::nullopt; +} + static std::optional Handler_OpTypePointer_StorageUniform16( const Instruction* instruction) { assert(instruction->opcode() == spv::Op::OpTypePointer && @@ -388,7 +419,7 @@ Handler_OpImageSparseRead_StorageImageReadWithoutFormat( } // Opcode of interest to determine capabilities requirements. -constexpr std::array, 13> kOpcodeHandlers{{ +constexpr std::array, 14> kOpcodeHandlers{{ // clang-format off {spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat}, {spv::Op::OpImageWrite, Handler_OpImageWrite_StorageImageWriteWithoutFormat}, @@ -403,6 +434,7 @@ constexpr std::array, 13> kOpcodeHandlers{{ {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16}, {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniform16}, {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageUniformBufferBlock16}, + {spv::Op::OpTypePointer, Handler_OpTypePointer_StorageBuffer16BitAccess}, // clang-format on }}; diff --git a/third_party/spirv-tools/source/opt/trim_capabilities_pass.h b/third_party/spirv-tools/source/opt/trim_capabilities_pass.h index 1d1183ab23..ec4ee3abaa 100644 --- a/third_party/spirv-tools/source/opt/trim_capabilities_pass.h +++ b/third_party/spirv-tools/source/opt/trim_capabilities_pass.h @@ -99,6 +99,7 @@ class TrimCapabilitiesPass : public Pass { spv::Capability::RayTraversalPrimitiveCullingKHR, spv::Capability::Shader, spv::Capability::ShaderClockKHR, + spv::Capability::StorageBuffer16BitAccess, spv::Capability::StorageImageReadWithoutFormat, spv::Capability::StorageImageWriteWithoutFormat, spv::Capability::StorageInputOutput16, diff --git a/third_party/spirv-tools/source/opt/type_manager.cpp b/third_party/spirv-tools/source/opt/type_manager.cpp index 45d14af531..e0c4b80161 100644 --- a/third_party/spirv-tools/source/opt/type_manager.cpp +++ b/third_party/spirv-tools/source/opt/type_manager.cpp @@ -495,6 +495,8 @@ uint32_t TypeManager::GetTypeInstruction(const Type* type) { break; } context()->AddType(std::move(typeInst)); + // TODO(dneto): This next call to AnalyzeDefUse is redundant becaues + // IRContext::AddType already does it. context()->AnalyzeDefUse(&*--context()->types_values_end()); AttachDecorations(id, type); return id; diff --git a/third_party/spirv-tools/source/spirv_validator_options.cpp b/third_party/spirv-tools/source/spirv_validator_options.cpp index fb09d5cf10..a9591f6b35 100644 --- a/third_party/spirv-tools/source/spirv_validator_options.cpp +++ b/third_party/spirv-tools/source/spirv_validator_options.cpp @@ -131,6 +131,11 @@ void spvValidatorOptionsSetAllowOffsetTextureOperand( options->allow_offset_texture_operand = val; } +void spvValidatorOptionsSetAllowVulkan32BitBitwise( + spv_validator_options options, bool val) { + options->allow_vulkan_32_bit_bitwise = val; +} + void spvValidatorOptionsSetFriendlyNames(spv_validator_options options, bool val) { options->use_friendly_names = val; diff --git a/third_party/spirv-tools/source/spirv_validator_options.h b/third_party/spirv-tools/source/spirv_validator_options.h index b794981b2e..9f0c125436 100644 --- a/third_party/spirv-tools/source/spirv_validator_options.h +++ b/third_party/spirv-tools/source/spirv_validator_options.h @@ -49,6 +49,7 @@ struct spv_validator_options_t { skip_block_layout(false), allow_localsizeid(false), allow_offset_texture_operand(false), + allow_vulkan_32_bit_bitwise(false), before_hlsl_legalization(false), use_friendly_names(true) {} @@ -62,6 +63,7 @@ struct spv_validator_options_t { bool skip_block_layout; bool allow_localsizeid; bool allow_offset_texture_operand; + bool allow_vulkan_32_bit_bitwise; bool before_hlsl_legalization; bool use_friendly_names; }; diff --git a/third_party/spirv-tools/source/text.cpp b/third_party/spirv-tools/source/text.cpp index 36518bef20..156d96272c 100644 --- a/third_party/spirv-tools/source/text.cpp +++ b/third_party/spirv-tools/source/text.cpp @@ -515,6 +515,124 @@ spv_result_t encodeInstructionStartingWithImmediate( return SPV_SUCCESS; } +/// @brief Translate an instruction started by OpUnknown and the following +/// operands to binary form +/// +/// @param[in] grammar the grammar to use for compilation +/// @param[in, out] context the dynamic compilation info +/// @param[out] pInst returned binary Opcode +/// +/// @return result code +spv_result_t encodeInstructionStartingWithOpUnknown( + const spvtools::AssemblyGrammar& grammar, + spvtools::AssemblyContext* context, spv_instruction_t* pInst) { + spv_position_t nextPosition = {}; + + uint16_t opcode; + uint16_t wordCount; + + // The '(' character. + if (context->advance()) + return context->diagnostic() << "Expected '(', found end of stream."; + if ('(' != context->peek()) { + return context->diagnostic() << "'(' expected after OpUnknown but found '" + << context->peek() << "'."; + } + context->seekForward(1); + + // The opcode enumerant. + if (context->advance()) + return context->diagnostic() + << "Expected opcode enumerant, found end of stream."; + std::string opcodeString; + spv_result_t error = context->getWord(&opcodeString, &nextPosition); + if (error) return context->diagnostic(error) << "Internal Error"; + + if (!spvtools::utils::ParseNumber(opcodeString.c_str(), &opcode)) { + return context->diagnostic() + << "Invalid opcode enumerant: \"" << opcodeString << "\"."; + } + + context->setPosition(nextPosition); + + // The ',' character. + if (context->advance()) + return context->diagnostic() << "Expected ',', found end of stream."; + if (',' != context->peek()) { + return context->diagnostic() + << "',' expected after opcode enumerant but found '" + << context->peek() << "'."; + } + context->seekForward(1); + + // The number of words. + if (context->advance()) + return context->diagnostic() + << "Expected number of words, found end of stream."; + std::string wordCountString; + error = context->getWord(&wordCountString, &nextPosition); + if (error) return context->diagnostic(error) << "Internal Error"; + + if (!spvtools::utils::ParseNumber(wordCountString.c_str(), &wordCount)) { + return context->diagnostic() + << "Invalid number of words: \"" << wordCountString << "\"."; + } + + if (wordCount == 0) { + return context->diagnostic() << "Number of words (which includes the " + "opcode) must be greater than zero."; + } + + context->setPosition(nextPosition); + + // The ')' character. + if (context->advance()) + return context->diagnostic() << "Expected ')', found end of stream."; + if (')' != context->peek()) { + return context->diagnostic() + << "')' expected after number of words but found '" + << context->peek() << "'."; + } + context->seekForward(1); + + pInst->opcode = static_cast(opcode); + context->binaryEncodeU32(spvOpcodeMake(wordCount, pInst->opcode), pInst); + + wordCount--; // Subtract the opcode from the number of words left to read. + + while (wordCount-- > 0) { + if (context->advance() == SPV_END_OF_STREAM) { + return context->diagnostic() << "Expected " << wordCount + 1 + << " more operands, found end of stream."; + } + if (context->isStartOfNewInst()) { + std::string invalid; + context->getWord(&invalid, &nextPosition); + return context->diagnostic() + << "Unexpected start of new instruction: \"" << invalid + << "\". Expected " << wordCount + 1 << " more operands"; + } + + std::string operandValue; + if ((error = context->getWord(&operandValue, &nextPosition))) + return context->diagnostic(error) << "Internal Error"; + + if (operandValue == "=") + return context->diagnostic() << "OpUnknown not allowed before =."; + + // Needed to pass to spvTextEncodeOpcode(), but it shouldn't ever be + // expanded. + spv_operand_pattern_t dummyExpectedOperands; + error = spvTextEncodeOperand( + grammar, context, SPV_OPERAND_TYPE_OPTIONAL_CIV, operandValue.c_str(), + pInst, &dummyExpectedOperands); + if (error) return error; + context->setPosition(nextPosition); + } + + return SPV_SUCCESS; +} + /// @brief Translate single Opcode and operands to binary form /// /// @param[in] grammar the grammar to use for compilation @@ -574,6 +692,16 @@ spv_result_t spvTextEncodeOpcode(const spvtools::AssemblyGrammar& grammar, } } + if (opcodeName == "OpUnknown") { + if (!result_id.empty()) { + return context->diagnostic() + << "OpUnknown not allowed in assignment. Use an explicit result " + "id operand instead."; + } + context->setPosition(nextPosition); + return encodeInstructionStartingWithOpUnknown(grammar, context, pInst); + } + // NOTE: The table contains Opcode names without the "Op" prefix. const char* pInstName = opcodeName.data() + 2; diff --git a/third_party/spirv-tools/source/text_handler.cpp b/third_party/spirv-tools/source/text_handler.cpp index a778c2c143..ca8859ecd9 100644 --- a/third_party/spirv-tools/source/text_handler.cpp +++ b/third_party/spirv-tools/source/text_handler.cpp @@ -118,6 +118,9 @@ spv_result_t getWord(spv_text text, spv_position position, std::string* word) { break; case ' ': case ';': + case ',': + case '(': + case ')': case '\t': case '\n': case '\r': diff --git a/third_party/spirv-tools/source/util/small_vector.h b/third_party/spirv-tools/source/util/small_vector.h index 1351475bd8..1838843876 100644 --- a/third_party/spirv-tools/source/util/small_vector.h +++ b/third_party/spirv-tools/source/util/small_vector.h @@ -43,6 +43,7 @@ namespace utils { template class SmallVector { public: + using value_type = T; using iterator = T*; using const_iterator = const T*; diff --git a/third_party/spirv-tools/source/util/string_utils.h b/third_party/spirv-tools/source/util/string_utils.h index 03e20b3d63..590f8cf458 100644 --- a/third_party/spirv-tools/source/util/string_utils.h +++ b/third_party/spirv-tools/source/util/string_utils.h @@ -17,13 +17,12 @@ #include +#include #include #include #include #include -#include "source/util/string_utils.h" - namespace spvtools { namespace utils { @@ -48,8 +47,9 @@ std::pair SplitFlagArgs(const std::string& flag); // Encodes a string as a sequence of words, using the SPIR-V encoding, appending // to an existing vector. -inline void AppendToVector(const std::string& input, - std::vector* result) { +template > +inline void AppendToVector(const std::string& input, VectorType* result) { + static_assert(std::is_same::value); uint32_t word = 0; size_t num_bytes = input.size(); // SPIR-V strings are null-terminated. The byte_index == num_bytes @@ -70,8 +70,10 @@ inline void AppendToVector(const std::string& input, } // Encodes a string as a sequence of words, using the SPIR-V encoding. -inline std::vector MakeVector(const std::string& input) { - std::vector result; +template > +inline VectorType MakeVector(const std::string& input) { + static_assert(std::is_same::value); + VectorType result; AppendToVector(input, &result); return result; } diff --git a/third_party/spirv-tools/source/val/validate_bitwise.cpp b/third_party/spirv-tools/source/val/validate_bitwise.cpp index bb0588a09f..4acc998225 100644 --- a/third_party/spirv-tools/source/val/validate_bitwise.cpp +++ b/third_party/spirv-tools/source/val/validate_bitwise.cpp @@ -30,14 +30,14 @@ spv_result_t ValidateBaseType(ValidationState_t& _, const Instruction* inst, if (!_.IsIntScalarType(base_type) && !_.IsIntVectorType(base_type)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) - << _.VkErrorID(4781) << "Expected int scalar or vector type for Base operand: " << spvOpcodeString(opcode); } // Vulkan has a restriction to 32 bit for base if (spvIsVulkanEnv(_.context()->target_env)) { - if (_.GetBitWidth(base_type) != 32) { + if (_.GetBitWidth(base_type) != 32 && + !_.options()->allow_vulkan_32_bit_bitwise) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << _.VkErrorID(4781) << "Expected 32-bit int type for Base operand: " diff --git a/third_party/spirv-tools/source/val/validate_decorations.cpp b/third_party/spirv-tools/source/val/validate_decorations.cpp index 256bf0f6f1..f98512744e 100644 --- a/third_party/spirv-tools/source/val/validate_decorations.cpp +++ b/third_party/spirv-tools/source/val/validate_decorations.cpp @@ -1916,7 +1916,7 @@ spv_result_t CheckComponentDecoration(ValidationState_t& vstate, if (!vstate.IsIntScalarOrVectorType(type_id) && !vstate.IsFloatScalarOrVectorType(type_id)) { return vstate.diag(SPV_ERROR_INVALID_ID, &inst) - << vstate.VkErrorID(4924) + << vstate.VkErrorID(10583) << "Component decoration specified for type " << vstate.getIdName(type_id) << " that is not a scalar or vector"; } @@ -2083,6 +2083,204 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) { return SPV_SUCCESS; } +bool AllowsLayout(ValidationState_t& vstate, const spv::StorageClass sc) { + switch (sc) { + case spv::StorageClass::StorageBuffer: + case spv::StorageClass::Uniform: + case spv::StorageClass::PhysicalStorageBuffer: + case spv::StorageClass::PushConstant: + // Always explicitly laid out. + return true; + case spv::StorageClass::UniformConstant: + return false; + case spv::StorageClass::Workgroup: + return vstate.HasCapability( + spv::Capability::WorkgroupMemoryExplicitLayoutKHR); + case spv::StorageClass::Function: + case spv::StorageClass::Private: + return vstate.version() <= SPV_SPIRV_VERSION_WORD(1, 4); + case spv::StorageClass::Input: + case spv::StorageClass::Output: + // Block is used generally and mesh shaders use Offset. + return true; + default: + // TODO: Some storage classes in ray tracing use explicit layout + // decorations, but it is not well documented which. For now treat other + // storage classes as allowed to be laid out. See Vulkan internal issue + // 4192. + return true; + } +} + +bool UsesExplicitLayout(ValidationState_t& vstate, uint32_t type_id, + std::unordered_map& cache) { + if (type_id == 0) { + return false; + } + + if (cache.count(type_id)) { + return cache[type_id]; + } + + bool res = false; + const auto type_inst = vstate.FindDef(type_id); + if (type_inst->opcode() == spv::Op::OpTypeStruct || + type_inst->opcode() == spv::Op::OpTypeArray || + type_inst->opcode() == spv::Op::OpTypeRuntimeArray || + type_inst->opcode() == spv::Op::OpTypePointer || + type_inst->opcode() == spv::Op::OpTypeUntypedPointerKHR) { + const auto& id_decs = vstate.id_decorations(); + const auto iter = id_decs.find(type_id); + if (iter != id_decs.end()) { + bool allowLayoutDecorations = false; + if (type_inst->opcode() == spv::Op::OpTypePointer) { + const auto sc = type_inst->GetOperandAs(1); + allowLayoutDecorations = AllowsLayout(vstate, sc); + } + if (!allowLayoutDecorations) { + res = std::any_of( + iter->second.begin(), iter->second.end(), [](const Decoration& d) { + return d.dec_type() == spv::Decoration::Block || + d.dec_type() == spv::Decoration::BufferBlock || + d.dec_type() == spv::Decoration::Offset || + d.dec_type() == spv::Decoration::ArrayStride || + d.dec_type() == spv::Decoration::MatrixStride; + }); + } + } + + if (!res) { + switch (type_inst->opcode()) { + case spv::Op::OpTypeStruct: + for (uint32_t i = 1; !res && i < type_inst->operands().size(); i++) { + res = UsesExplicitLayout( + vstate, type_inst->GetOperandAs(i), cache); + } + break; + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: + res = UsesExplicitLayout(vstate, type_inst->GetOperandAs(1), + cache); + break; + case spv::Op::OpTypePointer: { + const auto sc = type_inst->GetOperandAs(1); + if (!AllowsLayout(vstate, sc)) { + res = UsesExplicitLayout( + vstate, type_inst->GetOperandAs(2), cache); + } + } + default: + break; + } + } + } + + cache[type_id] = res; + return res; +} + +spv_result_t CheckInvalidVulkanExplicitLayout(ValidationState_t& vstate) { + if (!spvIsVulkanEnv(vstate.context()->target_env)) { + return SPV_SUCCESS; + } + + std::unordered_map cache; + for (const auto& inst : vstate.ordered_instructions()) { + const auto type_id = inst.type_id(); + const auto type_inst = vstate.FindDef(type_id); + uint32_t fail_id = 0; + // Variables are the main place to check for improper decorations, but some + // untyped pointer instructions must also be checked since those types may + // never be instantiated by a variable. Unlike verifying a valid layout, + // physical storage buffer does not need checked here since it is always + // explicitly laid out. + switch (inst.opcode()) { + case spv::Op::OpVariable: + case spv::Op::OpUntypedVariableKHR: { + const auto sc = inst.GetOperandAs(2); + auto check_id = type_id; + if (inst.opcode() == spv::Op::OpUntypedVariableKHR) { + if (inst.operands().size() > 3) { + check_id = inst.GetOperandAs(3); + } + } + if (!AllowsLayout(vstate, sc) && + UsesExplicitLayout(vstate, check_id, cache)) { + fail_id = check_id; + } + break; + } + case spv::Op::OpUntypedAccessChainKHR: + case spv::Op::OpUntypedInBoundsAccessChainKHR: + case spv::Op::OpUntypedPtrAccessChainKHR: + case spv::Op::OpUntypedInBoundsPtrAccessChainKHR: { + // Check both the base type and return type. The return type may have an + // invalid array stride. + const auto sc = type_inst->GetOperandAs(1); + const auto base_type_id = inst.GetOperandAs(2); + if (!AllowsLayout(vstate, sc)) { + if (UsesExplicitLayout(vstate, base_type_id, cache)) { + fail_id = base_type_id; + } else if (UsesExplicitLayout(vstate, type_id, cache)) { + fail_id = type_id; + } + } + break; + } + case spv::Op::OpUntypedArrayLengthKHR: { + // Check the data type. + const auto ptr_ty_id = + vstate.FindDef(inst.GetOperandAs(3))->type_id(); + const auto ptr_ty = vstate.FindDef(ptr_ty_id); + const auto sc = ptr_ty->GetOperandAs(1); + const auto base_type_id = inst.GetOperandAs(2); + if (!AllowsLayout(vstate, sc) && + UsesExplicitLayout(vstate, base_type_id, cache)) { + fail_id = base_type_id; + } + break; + } + case spv::Op::OpLoad: { + const auto ptr_id = inst.GetOperandAs(2); + const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id()); + if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) { + // For untyped pointers check the return type for an invalid layout. + const auto sc = ptr_type->GetOperandAs(1); + if (!AllowsLayout(vstate, sc) && + UsesExplicitLayout(vstate, type_id, cache)) { + fail_id = type_id; + } + } + break; + } + case spv::Op::OpStore: { + const auto ptr_id = inst.GetOperandAs(1); + const auto ptr_type = vstate.FindDef(vstate.FindDef(ptr_id)->type_id()); + if (ptr_type->opcode() == spv::Op::OpTypeUntypedPointerKHR) { + // For untyped pointers, check the type of the data operand for an + // invalid layout. + const auto sc = ptr_type->GetOperandAs(1); + const auto data_type_id = vstate.GetOperandTypeId(&inst, 2); + if (!AllowsLayout(vstate, sc) && + UsesExplicitLayout(vstate, data_type_id, cache)) { + fail_id = inst.GetOperandAs(2); + } + } + break; + } + default: + break; + } + if (fail_id != 0) { + return vstate.diag(SPV_ERROR_INVALID_ID, &inst) + << "Invalid explicit layout decorations on type for operand " + << vstate.getIdName(fail_id); + } + } + + return SPV_SUCCESS; +} + } // namespace spv_result_t ValidateDecorations(ValidationState_t& vstate) { @@ -2094,6 +2292,7 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) { if (auto error = CheckVulkanMemoryModelDeprecatedDecorations(vstate)) return error; if (auto error = CheckDecorationsFromDecoration(vstate)) return error; + if (auto error = CheckInvalidVulkanExplicitLayout(vstate)) return error; return SPV_SUCCESS; } diff --git a/third_party/spirv-tools/source/val/validate_function.cpp b/third_party/spirv-tools/source/val/validate_function.cpp index 4879a7db5f..50ae0a2dbd 100644 --- a/third_party/spirv-tools/source/val/validate_function.cpp +++ b/third_party/spirv-tools/source/val/validate_function.cpp @@ -258,7 +258,8 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _, _.HasCapability(spv::Capability::VariablePointers) && sc == spv::StorageClass::Workgroup; const bool uc_ptr = sc == spv::StorageClass::UniformConstant; - if (!ssbo_vptr && !wg_vptr && !uc_ptr) { + if (!_.options()->before_hlsl_legalization && !ssbo_vptr && + !wg_vptr && !uc_ptr) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Pointer operand " << _.getIdName(argument_id) << " must be a memory object declaration"; diff --git a/third_party/spirv-tools/source/val/validate_interfaces.cpp b/third_party/spirv-tools/source/val/validate_interfaces.cpp index 8b96dc8248..06bae04e5d 100644 --- a/third_party/spirv-tools/source/val/validate_interfaces.cpp +++ b/third_party/spirv-tools/source/val/validate_interfaces.cpp @@ -48,6 +48,29 @@ bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) { } } +// Special validation for varibles that are between shader stages +spv_result_t ValidateInputOutputInterfaceVariables(ValidationState_t& _, + const Instruction* var) { + auto var_pointer = _.FindDef(var->GetOperandAs(0)); + uint32_t pointer_id = var_pointer->GetOperandAs(2); + + const auto isPhysicalStorageBuffer = [](const Instruction* insn) { + return insn->opcode() == spv::Op::OpTypePointer && + insn->GetOperandAs(1) == + spv::StorageClass::PhysicalStorageBuffer; + }; + + if (_.ContainsType(pointer_id, isPhysicalStorageBuffer)) { + return _.diag(SPV_ERROR_INVALID_ID, var) + << _.VkErrorID(9557) << "Input/Output interface variable id <" + << var->id() + << "> contains a PhysicalStorageBuffer pointer, which is not " + "allowed. If you want to interface shader stages with a " + "PhysicalStorageBuffer, cast to a uint64 or uvec2 instead."; + } + return SPV_SUCCESS; +} + // Checks that \c var is listed as an interface in all the entry points that use // it. spv_result_t check_interface_variable(ValidationState_t& _, @@ -107,6 +130,12 @@ spv_result_t check_interface_variable(ValidationState_t& _, } } + if (var->GetOperandAs(2) == spv::StorageClass::Input || + var->GetOperandAs(2) == spv::StorageClass::Output) { + if (auto error = ValidateInputOutputInterfaceVariables(_, var)) + return error; + } + return SPV_SUCCESS; } @@ -135,17 +164,22 @@ spv_result_t NumConsumedLocations(ValidationState_t& _, const Instruction* type, } break; case spv::Op::OpTypeMatrix: - // Matrices consume locations equal to the underlying vector type for - // each column. - NumConsumedLocations(_, _.FindDef(type->GetOperandAs(1)), - num_locations); + // Matrices consume locations equivalent to arrays of 4-component vectors. + if (_.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeInt, 64) || + _.ContainsSizedIntOrFloatType(type->id(), spv::Op::OpTypeFloat, 64)) { + *num_locations = 2; + } else { + *num_locations = 1; + } *num_locations *= type->GetOperandAs(2); break; case spv::Op::OpTypeArray: { // Arrays consume locations equal to the underlying type times the number // of elements in the vector. - NumConsumedLocations(_, _.FindDef(type->GetOperandAs(1)), - num_locations); + if (auto error = NumConsumedLocations( + _, _.FindDef(type->GetOperandAs(1)), num_locations)) { + return error; + } bool is_int = false; bool is_const = false; uint32_t value = 0; @@ -215,10 +249,31 @@ uint32_t NumConsumedComponents(ValidationState_t& _, const Instruction* type) { NumConsumedComponents(_, _.FindDef(type->GetOperandAs(1))); num_components *= type->GetOperandAs(2); break; - case spv::Op::OpTypeArray: - // Skip the array. - return NumConsumedComponents(_, - _.FindDef(type->GetOperandAs(1))); + case spv::Op::OpTypeMatrix: + // Matrices consume all components of the location. + // Round up to next multiple of 4. + num_components = + NumConsumedComponents(_, _.FindDef(type->GetOperandAs(1))); + num_components *= type->GetOperandAs(2); + num_components = ((num_components + 3) / 4) * 4; + break; + case spv::Op::OpTypeArray: { + // Arrays consume all components of the location. + // Round up to next multiple of 4. + num_components = + NumConsumedComponents(_, _.FindDef(type->GetOperandAs(1))); + + bool is_int = false; + bool is_const = false; + uint32_t value = 0; + // Attempt to evaluate the number of array elements. + std::tie(is_int, is_const, value) = + _.EvalInt32IfConst(type->GetOperandAs(2)); + if (is_int && is_const) num_components *= value; + + num_components = ((num_components + 3) / 4) * 4; + return num_components; + } case spv::Op::OpTypePointer: if (_.addressing_model() == spv::AddressingModel::PhysicalStorageBuffer64 && @@ -301,9 +356,10 @@ spv_result_t GetLocationsForVariable( } } - // Vulkan 14.1.3: Tessellation control and mesh per-vertex outputs and - // tessellation control, evaluation and geometry per-vertex inputs have a - // layer of arraying that is not included in interface matching. + // Vulkan 15.1.3 (Interface Matching): Tessellation control and mesh + // per-vertex outputs and tessellation control, evaluation and geometry + // per-vertex inputs have a layer of arraying that is not included in + // interface matching. bool is_arrayed = false; switch (entry_point->GetOperandAs(0)) { case spv::ExecutionModel::TessellationControl: @@ -357,51 +413,33 @@ spv_result_t GetLocationsForVariable( const std::string storage_class = is_output ? "output" : "input"; if (has_location) { - auto sub_type = type; - bool is_int = false; - bool is_const = false; - uint32_t array_size = 1; - // If the variable is still arrayed, mark the locations/components per - // index. - if (type->opcode() == spv::Op::OpTypeArray) { - // Determine the array size if possible and get the element type. - std::tie(is_int, is_const, array_size) = - _.EvalInt32IfConst(type->GetOperandAs(2)); - if (!is_int || !is_const) array_size = 1; - auto sub_type_id = type->GetOperandAs(1); - sub_type = _.FindDef(sub_type_id); + uint32_t num_locations = 0; + if (auto error = NumConsumedLocations(_, type, &num_locations)) + return error; + uint32_t num_components = NumConsumedComponents(_, type); + + uint32_t start = location * 4; + uint32_t end = (location + num_locations) * 4; + if (num_components % 4 != 0) { + start += component; + end = start + num_components; } - uint32_t num_locations = 0; - if (auto error = NumConsumedLocations(_, sub_type, &num_locations)) - return error; - uint32_t num_components = NumConsumedComponents(_, sub_type); + if (kMaxLocations <= start) { + // Too many locations, give up. + return SPV_SUCCESS; + } - for (uint32_t array_idx = 0; array_idx < array_size; ++array_idx) { - uint32_t array_location = location + (num_locations * array_idx); - uint32_t start = array_location * 4; - if (kMaxLocations <= start) { - // Too many locations, give up. - break; - } + auto locs = locations; + if (has_index && index == 1) locs = output_index1_locations; - uint32_t end = (array_location + num_locations) * 4; - if (num_components != 0) { - start += component; - end = array_location * 4 + component + num_components; - } - - auto locs = locations; - if (has_index && index == 1) locs = output_index1_locations; - - for (uint32_t i = start; i < end; ++i) { - if (!locs->insert(i).second) { - return _.diag(SPV_ERROR_INVALID_DATA, entry_point) - << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721)) - << "Entry-point has conflicting " << storage_class - << " location assignment at location " << i / 4 - << ", component " << i % 4; - } + for (uint32_t i = start; i < end; ++i) { + if (!locs->insert(i).second) { + return _.diag(SPV_ERROR_INVALID_DATA, entry_point) + << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721)) + << "Entry-point has conflicting " << storage_class + << " location assignment at location " << i / 4 << ", component " + << i % 4; } } } else { @@ -460,38 +498,19 @@ spv_result_t GetLocationsForVariable( continue; } - if (member->opcode() == spv::Op::OpTypeArray && num_components >= 1 && - num_components < 4) { - // When an array has an element that takes less than a location in - // size, calculate the used locations in a strided manner. - for (uint32_t l = location; l < num_locations + location; ++l) { - for (uint32_t c = component; c < component + num_components; ++c) { - uint32_t check = 4 * l + c; - if (!locations->insert(check).second) { - return _.diag(SPV_ERROR_INVALID_DATA, entry_point) - << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721)) - << "Entry-point has conflicting " << storage_class - << " location assignment at location " << l - << ", component " << c; - } - } - } - } else { - // TODO: There is a hole here is the member is an array of 3- or - // 4-element vectors of 64-bit types. - uint32_t end = (location + num_locations) * 4; - if (num_components != 0) { - start += component; - end = location * 4 + component + num_components; - } - for (uint32_t l = start; l < end; ++l) { - if (!locations->insert(l).second) { - return _.diag(SPV_ERROR_INVALID_DATA, entry_point) - << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721)) - << "Entry-point has conflicting " << storage_class - << " location assignment at location " << l / 4 - << ", component " << l % 4; - } + uint32_t end = (location + num_locations) * 4; + if (num_components % 4 != 0) { + start += component; + end = location * 4 + component + num_components; + } + + for (uint32_t l = start; l < end; ++l) { + if (!locations->insert(l).second) { + return _.diag(SPV_ERROR_INVALID_DATA, entry_point) + << (is_output ? _.VkErrorID(8722) : _.VkErrorID(8721)) + << "Entry-point has conflicting " << storage_class + << " location assignment at location " << l / 4 + << ", component " << l % 4; } } } diff --git a/third_party/spirv-tools/source/val/validate_memory.cpp b/third_party/spirv-tools/source/val/validate_memory.cpp index ba45fa84d5..86ae759c6a 100644 --- a/third_party/spirv-tools/source/val/validate_memory.cpp +++ b/third_party/spirv-tools/source/val/validate_memory.cpp @@ -380,6 +380,16 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst, << _.VkErrorID(4708) << "Memory accesses with PhysicalStorageBuffer must use Aligned."; } + } else { + // even if there are other masks, the Aligned operand will be next + const uint32_t aligned_value = inst->GetOperandAs(index + 1); + const bool is_power_of_two = + aligned_value && !(aligned_value & (aligned_value - 1)); + if (!is_power_of_two) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Memory accesses Aligned operand value " << aligned_value + << " is not a power of two."; + } } return SPV_SUCCESS; @@ -2781,17 +2791,42 @@ spv_result_t ValidatePtrComparison(ValidationState_t& _, const auto op1 = _.FindDef(inst->GetOperandAs(2u)); const auto op2 = _.FindDef(inst->GetOperandAs(3u)); - if (!op1 || !op2 || op1->type_id() != op2->type_id()) { - return _.diag(SPV_ERROR_INVALID_ID, inst) - << "The types of Operand 1 and Operand 2 must match"; - } const auto op1_type = _.FindDef(op1->type_id()); + const auto op2_type = _.FindDef(op2->type_id()); if (!op1_type || (op1_type->opcode() != spv::Op::OpTypePointer && op1_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) { return _.diag(SPV_ERROR_INVALID_ID, inst) << "Operand type must be a pointer"; } + if (!op2_type || (op2_type->opcode() != spv::Op::OpTypePointer && + op2_type->opcode() != spv::Op::OpTypeUntypedPointerKHR)) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Operand type must be a pointer"; + } + + if (inst->opcode() == spv::Op::OpPtrDiff) { + if (op1->type_id() != op2->type_id()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The types of Operand 1 and Operand 2 must match"; + } + } else { + const auto either_untyped = + op1_type->opcode() == spv::Op::OpTypeUntypedPointerKHR || + op2_type->opcode() == spv::Op::OpTypeUntypedPointerKHR; + if (either_untyped) { + const auto sc1 = op1_type->GetOperandAs(1); + const auto sc2 = op2_type->GetOperandAs(1); + if (sc1 != sc2) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Pointer storage classes must match"; + } + } else if (op1->type_id() != op2->type_id()) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "The types of Operand 1 and Operand 2 must match"; + } + } + spv::StorageClass sc = op1_type->GetOperandAs(1u); if (_.addressing_model() == spv::AddressingModel::Logical) { if (sc != spv::StorageClass::Workgroup && diff --git a/third_party/spirv-tools/source/val/validation_state.cpp b/third_party/spirv-tools/source/val/validation_state.cpp index 4604d6da51..01d299842b 100644 --- a/third_party/spirv-tools/source/val/validation_state.cpp +++ b/third_party/spirv-tools/source/val/validation_state.cpp @@ -2415,8 +2415,6 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-StandaloneSpirv-Component-04922); case 4923: return VUID_WRAP(VUID-StandaloneSpirv-Component-04923); - case 4924: - return VUID_WRAP(VUID-StandaloneSpirv-Component-04924); case 6201: return VUID_WRAP(VUID-StandaloneSpirv-Flat-06201); case 6202: @@ -2525,6 +2523,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id, return VUID_WRAP(VUID-StandaloneSpirv-OpEntryPoint-08722); case 8973: return VUID_WRAP(VUID-StandaloneSpirv-Pointer-08973); + case 9557: + return VUID_WRAP(VUID-StandaloneSpirv-Input-09557); case 9638: return VUID_WRAP(VUID-StandaloneSpirv-OpTypeImage-09638); case 9658: @@ -2534,6 +2534,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id, case 10213: // This use to be a standalone, but maintenance8 will set allow_offset_texture_operand now return VUID_WRAP(VUID-RuntimeSpirv-Offset-10213); + case 10583: + return VUID_WRAP(VUID-StandaloneSpirv-Component-10583); default: return ""; // unknown id } diff --git a/third_party/spirv-tools/test/CMakeLists.txt b/third_party/spirv-tools/test/CMakeLists.txt index 119e9c9f25..5387a9795e 100644 --- a/third_party/spirv-tools/test/CMakeLists.txt +++ b/third_party/spirv-tools/test/CMakeLists.txt @@ -116,6 +116,7 @@ set(TEST_SOURCES libspirv_macros_test.cpp named_id_test.cpp name_mapper_test.cpp + op_unknown_test.cpp opcode_make_test.cpp opcode_require_capabilities_test.cpp opcode_split_test.cpp diff --git a/third_party/spirv-tools/test/diff/diff_files/diff_test_files_autogen.cmake b/third_party/spirv-tools/test/diff/diff_files/diff_test_files_autogen.cmake index 51cb62fab0..ce687d52c1 100644 --- a/third_party/spirv-tools/test/diff/diff_files/diff_test_files_autogen.cmake +++ b/third_party/spirv-tools/test/diff/diff_files/diff_test_files_autogen.cmake @@ -30,6 +30,8 @@ list(APPEND DIFF_TEST_FILES "diff_files/different_decorations_vertex_autogen.cpp" "diff_files/different_function_parameter_count_autogen.cpp" "diff_files/extra_if_block_autogen.cpp" +"diff_files/function_group_by_full_type_autogen.cpp" +"diff_files/function_group_by_mapped_id_autogen.cpp" "diff_files/index_signedness_autogen.cpp" "diff_files/int_vs_uint_constants_autogen.cpp" "diff_files/large_functions_large_diffs_autogen.cpp" @@ -44,5 +46,6 @@ list(APPEND DIFF_TEST_FILES "diff_files/spec_constant_composite_autogen.cpp" "diff_files/spec_constant_op_autogen.cpp" "diff_files/spec_constant_specid_autogen.cpp" +"diff_files/string_in_ext_inst_autogen.cpp" "diff_files/unrelated_shaders_autogen.cpp" ) diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_autogen.cpp b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_autogen.cpp new file mode 100644 index 0000000000..1843425f49 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_autogen.cpp @@ -0,0 +1,194 @@ +// GENERATED FILE - DO NOT EDIT. +// Generated by generate_tests.py +// +// Copyright (c) 2022 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../diff_test_utils.h" +#include "gtest/gtest.h" + +namespace spvtools { +namespace diff { +namespace { + +// Use functions' `OpFunctionType` to group before grouping by return type only. +constexpr char kSrc[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd)"; +constexpr char kDst[] = + R"(;; Use functions' `OpFunctionType` to group before grouping by return type only. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd +)"; + +TEST(DiffTest, FunctionGroupByFullType) { + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 + ; Bound: 15 + ; Schema: 0 + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %1 = OpTypeBool + %2 = OpTypeInt 32 1 + %3 = OpConstantNull %2 + %4 = OpTypeFunction %1 %2 + %5 = OpTypeFunction %1 %2 %2 + %6 = OpFunction %1 None %4 + %7 = OpFunctionParameter %2 + %8 = OpLabel + %9 = OpIEqual %1 %7 %3 + OpReturnValue %9 + OpFunctionEnd + %10 = OpFunction %1 None %5 + %11 = OpFunctionParameter %2 + %12 = OpFunctionParameter %2 + %13 = OpLabel + %14 = OpIEqual %1 %11 %3 + OpReturnValue %14 + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrc, kDst, kDiff, options); +} + +TEST(DiffTest, FunctionGroupByFullTypeNoDebug) { + constexpr char kSrcNoDebug[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd +)"; + constexpr char kDstNoDebug[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd +)"; + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 + ; Bound: 15 + ; Schema: 0 + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %1 = OpTypeBool + %2 = OpTypeInt 32 1 + %3 = OpConstantNull %2 + %4 = OpTypeFunction %1 %2 + %5 = OpTypeFunction %1 %2 %2 + %6 = OpFunction %1 None %4 + %7 = OpFunctionParameter %2 + %8 = OpLabel + %9 = OpIEqual %1 %7 %3 + OpReturnValue %9 + OpFunctionEnd + %10 = OpFunction %1 None %5 + %11 = OpFunctionParameter %2 + %12 = OpFunctionParameter %2 + %13 = OpLabel + %14 = OpIEqual %1 %11 %3 + OpReturnValue %14 + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options); +} + +} // namespace +} // namespace diff +} // namespace spvtools diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_dst.spvasm b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_dst.spvasm new file mode 100644 index 0000000000..eea527670c --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_dst.spvasm @@ -0,0 +1,25 @@ +;; Use functions' `OpFunctionType` to group before grouping by return type only. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_src.spvasm b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_src.spvasm new file mode 100644 index 0000000000..5cc7dd55a1 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_full_type_src.spvasm @@ -0,0 +1,25 @@ +;; Use functions' `OpFunctionType` to group before grouping by return type only. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %bool = OpTypeBool + %int = OpTypeInt 32 1 + %int_0 = OpConstantNull %int + + %fn_int = OpTypeFunction %bool %int + %fn_int_int = OpTypeFunction %bool %int %int + + %f = OpFunction %bool None %fn_int + %fp1 = OpFunctionParameter %int + %f_prologue = OpLabel + %fr = OpIEqual %bool %fp1 %int_0 + OpReturnValue %fr + OpFunctionEnd + + %g = OpFunction %bool None %fn_int_int + %gp1 = OpFunctionParameter %int + %gp2 = OpFunctionParameter %int + %g_prologue = OpLabel + %gr = OpIEqual %bool %gp1 %int_0 + OpReturnValue %gr + OpFunctionEnd diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_autogen.cpp b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_autogen.cpp new file mode 100644 index 0000000000..66e7bd96e3 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_autogen.cpp @@ -0,0 +1,181 @@ +// GENERATED FILE - DO NOT EDIT. +// Generated by generate_tests.py +// +// Copyright (c) 2022 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../diff_test_utils.h" +#include "gtest/gtest.h" + +namespace spvtools { +namespace diff { +namespace { + +// Don't forget to map between source and destination ids when grouping +// functions by return type. +constexpr char kSrc[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %1 = OpTypeInt 32 0 + %2 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %1 + %fn_int = OpTypeFunction %2 + + %uint_42 = OpConstant %1 42 + %int_1729 = OpConstant %2 1729 + + %f = OpFunction %1 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %2 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd)"; +constexpr char kDst[] = + R"(;; Don't forget to map between source and destination ids when grouping functions by return type. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %2 = OpTypeInt 32 0 + %1 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %2 + %fn_int = OpTypeFunction %1 + + %uint_42 = OpConstant %2 42 + %int_1729 = OpConstant %1 1729 + + %f = OpFunction %2 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %1 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd +)"; + +TEST(DiffTest, FunctionGroupByMappedId) { + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 + ; Bound: 12 + ; Schema: 0 + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %3 = OpTypeVoid + %1 = OpTypeInt 32 0 + %2 = OpTypeInt 32 1 + %4 = OpTypeFunction %1 + %5 = OpTypeFunction %2 + %6 = OpConstant %1 42 + %7 = OpConstant %2 1729 + %8 = OpFunction %1 None %4 + %9 = OpLabel + OpReturnValue %6 + OpFunctionEnd + %10 = OpFunction %2 None %5 + %11 = OpLabel + OpReturnValue %7 + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrc, kDst, kDiff, options); +} + +TEST(DiffTest, FunctionGroupByMappedIdNoDebug) { + constexpr char kSrcNoDebug[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %1 = OpTypeInt 32 0 + %2 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %1 + %fn_int = OpTypeFunction %2 + + %uint_42 = OpConstant %1 42 + %int_1729 = OpConstant %2 1729 + + %f = OpFunction %1 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %2 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd +)"; + constexpr char kDstNoDebug[] = R"( OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %2 = OpTypeInt 32 0 + %1 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %2 + %fn_int = OpTypeFunction %1 + + %uint_42 = OpConstant %2 42 + %int_1729 = OpConstant %1 1729 + + %f = OpFunction %2 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %1 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd +)"; + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 + ; Bound: 12 + ; Schema: 0 + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %3 = OpTypeVoid + %1 = OpTypeInt 32 0 + %2 = OpTypeInt 32 1 + %4 = OpTypeFunction %1 + %5 = OpTypeFunction %2 + %6 = OpConstant %1 42 + %7 = OpConstant %2 1729 + %8 = OpFunction %1 None %4 + %9 = OpLabel + OpReturnValue %6 + OpFunctionEnd + %10 = OpFunction %2 None %5 + %11 = OpLabel + OpReturnValue %7 + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options); +} + +} // namespace +} // namespace diff +} // namespace spvtools diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_dst.spvasm b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_dst.spvasm new file mode 100644 index 0000000000..6af4241f34 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_dst.spvasm @@ -0,0 +1,23 @@ +;; Don't forget to map between source and destination ids when grouping functions by return type. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %2 = OpTypeInt 32 0 + %1 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %2 + %fn_int = OpTypeFunction %1 + + %uint_42 = OpConstant %2 42 + %int_1729 = OpConstant %1 1729 + + %f = OpFunction %2 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %1 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd diff --git a/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_src.spvasm b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_src.spvasm new file mode 100644 index 0000000000..d920381b14 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/function_group_by_mapped_id_src.spvasm @@ -0,0 +1,23 @@ +;; Don't forget to map between source and destination ids when grouping functions by return type. + OpCapability Shader + OpCapability Linkage + OpMemoryModel Logical GLSL450 + %void = OpTypeVoid + %1 = OpTypeInt 32 0 + %2 = OpTypeInt 32 1 + + %fn_uint = OpTypeFunction %1 + %fn_int = OpTypeFunction %2 + + %uint_42 = OpConstant %1 42 + %int_1729 = OpConstant %2 1729 + + %f = OpFunction %1 None %fn_uint + %f_prologue = OpLabel + OpReturnValue %uint_42 + OpFunctionEnd + + %g = OpFunction %2 None %fn_int + %g_prologue = OpLabel + OpReturnValue %int_1729 + OpFunctionEnd diff --git a/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_autogen.cpp b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_autogen.cpp new file mode 100644 index 0000000000..fc752c93bb --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_autogen.cpp @@ -0,0 +1,205 @@ +// GENERATED FILE - DO NOT EDIT. +// Generated by generate_tests.py +// +// Copyright (c) 2022 Google LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "../diff_test_utils.h" + +#include "gtest/gtest.h" + +namespace spvtools { +namespace diff { +namespace { + +// Tests a diff where the an OpString is used only as parameter of OpExtInst. +constexpr char kSrc[] = R"( OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "unsigned == %u" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + OpName %main "main" + OpName %foo "foo" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd)"; +constexpr char kDst[] = R"( OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "signed == %d" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + OpName %main "main" + OpName %foo "foo" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd +)"; + +TEST(DiffTest, StringInExtInst) { + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 +-; Bound: 14 ++; Bound: 15 + ; Schema: 0 + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpExecutionMode %2 LocalSize 1 1 1 +-%10 = OpString "unsigned == %u" ++%14 = OpString "signed == %d" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + OpName %2 "main" + OpName %4 "foo" + %6 = OpTypeVoid + %3 = OpTypeFunction %6 + %7 = OpTypeInt 32 0 + %8 = OpTypePointer Function %7 + %9 = OpConstant %7 127 + %2 = OpFunction %6 None %3 + %5 = OpLabel + %4 = OpVariable %8 Function + OpStore %4 %9 + %11 = OpLoad %7 %4 +-%13 = OpExtInst %6 %12 1 %10 %11 ++%13 = OpExtInst %6 %12 1 %14 %11 + OpReturn + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrc, kDst, kDiff, options); +} + +TEST(DiffTest, StringInExtInstNoDebug) { + constexpr char kSrcNoDebug[] = R"( OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "unsigned == %u" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd +)"; + constexpr char kDstNoDebug[] = R"( OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "signed == %d" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd +)"; + constexpr char kDiff[] = R"( ; SPIR-V + ; Version: 1.6 + ; Generator: Khronos SPIR-V Tools Assembler; 0 +-; Bound: 14 ++; Bound: 15 + ; Schema: 0 + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpExecutionMode %2 LocalSize 1 1 1 +-%10 = OpString "unsigned == %u" ++%14 = OpString "signed == %d" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + %4 = OpTypeVoid + %3 = OpTypeFunction %4 + %6 = OpTypeInt 32 0 + %7 = OpTypePointer Function %6 + %8 = OpConstant %6 127 + %2 = OpFunction %4 None %3 + %5 = OpLabel + %9 = OpVariable %7 Function + OpStore %9 %8 + %11 = OpLoad %6 %9 +-%13 = OpExtInst %4 %12 1 %10 %11 ++%13 = OpExtInst %4 %12 1 %14 %11 + OpReturn + OpFunctionEnd +)"; + Options options; + DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options); +} + +} // namespace +} // namespace diff +} // namespace spvtools diff --git a/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_dst.spvasm b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_dst.spvasm new file mode 100644 index 0000000000..3681332d40 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_dst.spvasm @@ -0,0 +1,25 @@ + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "signed == %d" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + OpName %main "main" + OpName %foo "foo" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd diff --git a/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_src.spvasm b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_src.spvasm new file mode 100644 index 0000000000..dd55bcebd7 --- /dev/null +++ b/third_party/spirv-tools/test/diff/diff_files/string_in_ext_inst_src.spvasm @@ -0,0 +1,26 @@ +;; Tests a diff where the an OpString is used only as parameter of OpExtInst. + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + %1 = OpExtInstImport "GLSL.std.450" + %12 = OpExtInstImport "NonSemantic.DebugPrintf" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main" + OpExecutionMode %main LocalSize 1 1 1 + %10 = OpString "unsigned == %u" + OpSource GLSL 450 + OpSourceExtension "GL_EXT_debug_printf" + OpName %main "main" + OpName %foo "foo" + %void = OpTypeVoid + %3 = OpTypeFunction %void + %uint = OpTypeInt 32 0 +%_ptr_Function_uint = OpTypePointer Function %uint + %uint_127 = OpConstant %uint 127 + %main = OpFunction %void None %3 + %5 = OpLabel + %foo = OpVariable %_ptr_Function_uint Function + OpStore %foo %uint_127 + %11 = OpLoad %uint %foo + %13 = OpExtInst %void %12 1 %10 %11 + OpReturn + OpFunctionEnd diff --git a/third_party/spirv-tools/test/fuzz/transformation_set_memory_operands_mask_test.cpp b/third_party/spirv-tools/test/fuzz/transformation_set_memory_operands_mask_test.cpp index 44901f9c7f..d08b98cd1e 100644 --- a/third_party/spirv-tools/test/fuzz/transformation_set_memory_operands_mask_test.cpp +++ b/third_party/spirv-tools/test/fuzz/transformation_set_memory_operands_mask_test.cpp @@ -333,10 +333,12 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %3 = OpTypeFunction %2 %6 = OpTypeFloat 32 %7 = OpTypeStruct %6 %6 %6 + %1001 = OpTypeStruct %6 %6 %6 %8 = OpTypeInt 32 0 %9 = OpConstant %8 12 %10 = OpTypeArray %7 %9 - %11 = OpTypePointer Private %10 + %1002 = OpTypeArray %1001 %9 + %11 = OpTypePointer Private %1002 %12 = OpVariable %11 Private %15 = OpTypeStruct %10 %7 %16 = OpTypePointer Uniform %15 @@ -344,26 +346,32 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %18 = OpTypeInt 32 1 %19 = OpConstant %18 0 %20 = OpTypePointer Uniform %10 - %24 = OpTypePointer Private %7 + %24 = OpTypePointer Private %1001 %27 = OpTypePointer Private %6 %30 = OpConstant %18 1 - %132 = OpTypePointer Function %10 + %132 = OpTypePointer Function %1002 %135 = OpTypePointer Uniform %7 - %145 = OpTypePointer Function %7 + %145 = OpTypePointer Function %1001 %4 = OpFunction %2 None %3 %5 = OpLabel %133 = OpVariable %132 Function %21 = OpAccessChain %20 %17 %19 - OpCopyMemory %12 %21 Aligned 16 Nontemporal|Aligned 16 + %1003 = OpLoad %10 %21 Nontemporal|Aligned 16 + %1004 = OpCopyLogical %1002 %1003 + OpStore %12 %1004 Aligned 16 OpCopyMemory %133 %12 Volatile OpCopyMemory %133 %12 OpCopyMemory %133 %12 %136 = OpAccessChain %135 %17 %30 %138 = OpAccessChain %24 %12 %19 - OpCopyMemory %138 %136 None Aligned 16 - OpCopyMemory %138 %136 Aligned 16 + %1005 = OpLoad %7 %136 Aligned 16 + %1006 = OpCopyLogical %1001 %1005 + OpStore %138 %1006 + %1007 = OpLoad %7 %136 + %1008 = OpCopyLogical %1001 %1007 + OpStore %138 %1008 Aligned 16 %146 = OpAccessChain %145 %133 %30 - %147 = OpLoad %7 %146 Volatile|Nontemporal|Aligned 16 + %147 = OpLoad %1001 %146 Volatile|Nontemporal|Aligned 16 %148 = OpAccessChain %24 %12 %19 OpStore %148 %147 Nontemporal OpReturn @@ -382,14 +390,14 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { MakeUnique(context.get()), validator_options); { TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0), + MakeInstructionDescriptor(21, spv::Op::OpLoad, 0), (uint32_t)spv::MemoryAccessMask::Aligned | (uint32_t)spv::MemoryAccessMask::Volatile, - 1); + 0); // Bad: cannot remove aligned ASSERT_FALSE(TransformationSetMemoryOperandsMask( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0), - (uint32_t)spv::MemoryAccessMask::Volatile, 1) + MakeInstructionDescriptor(21, spv::Op::OpLoad, 0), + (uint32_t)spv::MemoryAccessMask::Volatile, 0) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); @@ -399,13 +407,13 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1), + MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0), (uint32_t)spv::MemoryAccessMask::Nontemporal | (uint32_t)spv::MemoryAccessMask::Volatile, 1); // Bad: cannot remove volatile ASSERT_FALSE(TransformationSetMemoryOperandsMask( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1), + MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 0), (uint32_t)spv::MemoryAccessMask::Nontemporal, 0) .IsApplicable(context.get(), transformation_context)); ASSERT_TRUE( @@ -417,7 +425,7 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { // Creates the first operand. TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2), + MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 1), (uint32_t)spv::MemoryAccessMask::Nontemporal | (uint32_t)spv::MemoryAccessMask::Volatile, 0); @@ -430,7 +438,7 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { // Creates both operands. TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 3), + MakeInstructionDescriptor(21, spv::Op::OpCopyMemory, 2), (uint32_t)spv::MemoryAccessMask::Nontemporal | (uint32_t)spv::MemoryAccessMask::Volatile, 1); @@ -442,13 +450,13 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0), + MakeInstructionDescriptor(138, spv::Op::OpLoad, 0), (uint32_t)spv::MemoryAccessMask::Aligned | (uint32_t)spv::MemoryAccessMask::Nontemporal, - 1); + 0); // Bad: the first mask is None, so Aligned cannot be added to it. ASSERT_FALSE(TransformationSetMemoryOperandsMask( - MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 0), + MakeInstructionDescriptor(138, spv::Op::OpStore, 0), (uint32_t)spv::MemoryAccessMask::Aligned | (uint32_t)spv::MemoryAccessMask::Nontemporal, 0) @@ -461,8 +469,8 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { { TransformationSetMemoryOperandsMask transformation( - MakeInstructionDescriptor(138, spv::Op::OpCopyMemory, 1), - (uint32_t)spv::MemoryAccessMask::Volatile, 1); + MakeInstructionDescriptor(138, spv::Op::OpLoad, 1), + (uint32_t)spv::MemoryAccessMask::Volatile, 0); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation, context.get(), @@ -522,10 +530,12 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %3 = OpTypeFunction %2 %6 = OpTypeFloat 32 %7 = OpTypeStruct %6 %6 %6 + %1001 = OpTypeStruct %6 %6 %6 %8 = OpTypeInt 32 0 %9 = OpConstant %8 12 %10 = OpTypeArray %7 %9 - %11 = OpTypePointer Private %10 + %1002 = OpTypeArray %1001 %9 + %11 = OpTypePointer Private %1002 %12 = OpVariable %11 Private %15 = OpTypeStruct %10 %7 %16 = OpTypePointer Uniform %15 @@ -533,26 +543,32 @@ TEST(TransformationSetMemoryOperandsMaskTest, Spirv14OrHigher) { %18 = OpTypeInt 32 1 %19 = OpConstant %18 0 %20 = OpTypePointer Uniform %10 - %24 = OpTypePointer Private %7 + %24 = OpTypePointer Private %1001 %27 = OpTypePointer Private %6 %30 = OpConstant %18 1 - %132 = OpTypePointer Function %10 + %132 = OpTypePointer Function %1002 %135 = OpTypePointer Uniform %7 - %145 = OpTypePointer Function %7 + %145 = OpTypePointer Function %1001 %4 = OpFunction %2 None %3 %5 = OpLabel %133 = OpVariable %132 Function %21 = OpAccessChain %20 %17 %19 - OpCopyMemory %12 %21 Aligned 16 Aligned|Volatile 16 + %1003 = OpLoad %10 %21 Aligned|Volatile 16 + %1004 = OpCopyLogical %1002 %1003 + OpStore %12 %1004 Aligned 16 OpCopyMemory %133 %12 Volatile Nontemporal|Volatile OpCopyMemory %133 %12 Nontemporal|Volatile OpCopyMemory %133 %12 None Nontemporal|Volatile %136 = OpAccessChain %135 %17 %30 %138 = OpAccessChain %24 %12 %19 - OpCopyMemory %138 %136 None Aligned|Nontemporal 16 - OpCopyMemory %138 %136 Aligned 16 Volatile + %1005 = OpLoad %7 %136 Aligned|Nontemporal 16 + %1006 = OpCopyLogical %1001 %1005 + OpStore %138 %1006 + %1007 = OpLoad %7 %136 Volatile + %1008 = OpCopyLogical %1001 %1007 + OpStore %138 %1008 Aligned 16 %146 = OpAccessChain %145 %133 %30 - %147 = OpLoad %7 %146 Volatile|Aligned 16 + %147 = OpLoad %1001 %146 Volatile|Aligned 16 %148 = OpAccessChain %24 %12 %19 OpStore %148 %147 None OpReturn diff --git a/third_party/spirv-tools/test/link/linker_fixture.h b/third_party/spirv-tools/test/link/linker_fixture.h index d005288cf0..08a2880082 100644 --- a/third_party/spirv-tools/test/link/linker_fixture.h +++ b/third_party/spirv-tools/test/link/linker_fixture.h @@ -155,6 +155,19 @@ class LinkerTest : public ::testing::Test { } } + void Match(const std::string& templateBody, + const spvtest::Binary& linked_binary) { + std::string result; + EXPECT_TRUE( + tools_.Disassemble(linked_binary, &result, disassemble_options_)) + << GetErrorMessage(); + auto match_res = effcee::Match(result, templateBody); + EXPECT_EQ(effcee::Result::Status::Ok, match_res.status()) + << match_res.message() << "\nExpanded from:\n" + << templateBody << "\nChecking result:\n" + << result; + } + // An alternative to ExpandAndCheck, which uses the |templateBody| as the // match pattern for the disassembled linked result. void ExpandAndMatch( @@ -165,15 +178,7 @@ class LinkerTest : public ::testing::Test { EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n" << templateBody; if (res == SPV_SUCCESS) { - std::string result; - EXPECT_TRUE( - tools_.Disassemble(linked_binary, &result, disassemble_options_)) - << GetErrorMessage(); - auto match_res = effcee::Match(result, templateBody); - EXPECT_EQ(effcee::Result::Status::Ok, match_res.status()) - << match_res.message() << "\nExpanded from:\n" - << templateBody << "\nChecking result:\n" - << result; + Match(templateBody, linked_binary); } } diff --git a/third_party/spirv-tools/test/link/matching_imports_to_exports_test.cpp b/third_party/spirv-tools/test/link/matching_imports_to_exports_test.cpp index 6e71da3260..db94cb8362 100644 --- a/third_party/spirv-tools/test/link/matching_imports_to_exports_test.cpp +++ b/third_party/spirv-tools/test/link/matching_imports_to_exports_test.cpp @@ -928,9 +928,16 @@ OpDecorate %1 LinkageAttributes "foo" Import %2 = OpTypeFloat 32 %1 = OpVariable %2 Uniform )"; + + const std::string matchTemplate = R"( +; CHECK-NOT: OpDecorate {{.*}} Import +; CHECK-NOT: OpDecorate {{.*}} LinkOnceODR +)"; + spvtest::Binary linked_binary; EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body2}, &linked_binary)) << GetErrorMessage(); + Match(matchTemplate, linked_binary); } TEST_F(MatchingImportsToExports, LinkOnceODRLinkageFunMultiple) { @@ -961,9 +968,16 @@ OpDecorate %1 LinkageAttributes "foo" Import %1 = OpFunction %2 None %3 OpFunctionEnd )"; + + const std::string matchTemplate = R"( +; CHECK-NOT: OpDecorate {{.*}} Import +; CHECK-NOT: OpDecorate {{.*}} LinkOnceODR +)"; + spvtest::Binary linked_binary; EXPECT_EQ(SPV_SUCCESS, AssembleAndLink({body1, body1, body2}, &linked_binary)) << GetErrorMessage(); + Match(matchTemplate, linked_binary); } TEST_F(MatchingImportsToExports, LinkOnceODRAndExport) { diff --git a/third_party/spirv-tools/test/op_unknown_test.cpp b/third_party/spirv-tools/test/op_unknown_test.cpp new file mode 100644 index 0000000000..d41174e9a6 --- /dev/null +++ b/third_party/spirv-tools/test/op_unknown_test.cpp @@ -0,0 +1,128 @@ +// Copyright (c) 2025 The Khronos Group Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "gmock/gmock.h" +#include "source/util/bitutils.h" +#include "test/test_fixture.h" + +namespace spvtools { +namespace utils { +namespace { + +using spvtest::Concatenate; +using spvtest::MakeInstruction; +using spvtest::ScopedContext; +using spvtest::TextToBinaryTest; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::StrEq; + +using OpUnknownTest = TextToBinaryTest; + +TEST_F(OpUnknownTest, OpUnknown) { + SetText("OpUnknown(255, 1)"); + ASSERT_EQ(SPV_SUCCESS, spvTextToBinary(ScopedContext().context, text.str, + text.length, &binary, &diagnostic)); + EXPECT_EQ(0x000100FFu, binary->code[5]); + if (diagnostic) { + spvDiagnosticPrint(diagnostic); + } +} + +TEST_F(OpUnknownTest, HandlesOperands) { + EXPECT_THAT(CompiledInstructions("OpUnknown(24, 4) %a %b %123"), + Eq(MakeInstruction(spv::Op::OpTypeMatrix, {1, 2, 3}))); + EXPECT_THAT(CompiledInstructions("OpUnknown(24, 4) !1 %b %123"), + Eq(MakeInstruction(spv::Op::OpTypeMatrix, {1, 1, 2}))); +} + +TEST_F(OpUnknownTest, HandlesWhitespace) { + EXPECT_THAT(CompiledInstructions("OpUnknown ( 24 , 4 ) %a %b %123"), + Eq(MakeInstruction(spv::Op::OpTypeMatrix, {1, 2, 3}))); + EXPECT_THAT(CompiledInstructions("OpUnknown(24,4) %a %b %123"), + Eq(MakeInstruction(spv::Op::OpTypeMatrix, {1, 2, 3}))); +} + +TEST_F(OpUnknownTest, MultipleInstructions) { + EXPECT_THAT( + CompiledInstructions( + "%a = OpTypeFunction %b\nOpUnknown(21, 5) %c %d 32 1\nOpNop"), + Eq(Concatenate({MakeInstruction(spv::Op::OpTypeFunction, {1, 2}), + MakeInstruction(spv::Op::OpTypeInt, {3, 4, 32, 1}), + MakeInstruction(spv::Op::OpNop, {})}))); +} + +TEST_F(OpUnknownTest, OpUnknownInAssignment) { + EXPECT_EQ( + "OpUnknown not allowed in assignment. Use an explicit result id operand " + "instead.", + CompileFailure("%2 = OpUnknown(22, 3) 32")); + EXPECT_EQ("OpUnknown not allowed before =.", + CompileFailure("OpUnknown(22, 3) = OpTypeFloat 32")); +} + +TEST_F(OpUnknownTest, ParsingErrors) { + EXPECT_EQ("Expected '(', found end of stream.", CompileFailure("OpUnknown")); + EXPECT_EQ("'(' expected after OpUnknown but found 'a'.", + CompileFailure("OpUnknown abc")); + + EXPECT_EQ("Expected opcode enumerant, found end of stream.", + CompileFailure("OpUnknown(")); + EXPECT_EQ("Invalid opcode enumerant: \"abc\".", + CompileFailure("OpUnknown(abc")); + // Opcode enumerant must fit in 16 bits. + EXPECT_EQ("Invalid opcode enumerant: \"70000\".", + CompileFailure("OpUnknown(70000")); + + EXPECT_EQ("Expected ',', found end of stream.", + CompileFailure("OpUnknown(22")); + EXPECT_EQ("',' expected after opcode enumerant but found 'a'.", + CompileFailure("OpUnknown(22 abc")); + + EXPECT_EQ("Expected number of words, found end of stream.", + CompileFailure("OpUnknown(22,")); + EXPECT_EQ("Invalid number of words: \"abc\".", + CompileFailure("OpUnknown(22, abc")); + // Number of words must fit in 16 bits. + EXPECT_EQ("Invalid number of words: \"70000\".", + CompileFailure("OpUnknown(22, 70000")); + EXPECT_EQ( + "Number of words (which includes the opcode) must be greater than zero.", + CompileFailure("OpUnknown(22, 0")); + + EXPECT_EQ("Expected ')', found end of stream.", + CompileFailure("OpUnknown(22, 3")); + EXPECT_EQ("')' expected after number of words but found 'a'.", + CompileFailure("OpUnknown(22, 3 abc")); + + EXPECT_EQ( + "Unexpected start of new instruction: \"OpNop\". Expected 2 more " + "operands", + CompileFailure("OpUnknown(22, 3) OpNop")); + EXPECT_EQ("Expected 2 more operands, found end of stream.", + CompileFailure("OpUnknown(22, 3)")); + + EXPECT_EQ(CompileFailure("OpUnknown(21, 4) %c %d 32 1"), + "Expected or at the beginning of an " + "instruction, found '1'."); +} + +} // namespace +} // namespace utils +} // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/CMakeLists.txt b/third_party/spirv-tools/test/opt/CMakeLists.txt index 4ef119deab..7e6e794679 100644 --- a/third_party/spirv-tools/test/opt/CMakeLists.txt +++ b/third_party/spirv-tools/test/opt/CMakeLists.txt @@ -94,10 +94,12 @@ add_spvtools_unittest(TARGET opt relax_float_ops_test.cpp replace_desc_array_access_using_var_index_test.cpp replace_invalid_opc_test.cpp + resolve_binding_conflicts_pass_test.cpp scalar_analysis.cpp scalar_replacement_test.cpp set_spec_const_default_value_test.cpp simplification_test.cpp + split_combined_image_sampler_pass_test.cpp spread_volatile_semantics_test.cpp strength_reduction_test.cpp strip_debug_info_test.cpp diff --git a/third_party/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp b/third_party/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp index f792ada73f..f06993a5f9 100644 --- a/third_party/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp +++ b/third_party/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp @@ -8297,6 +8297,185 @@ OpFunctionEnd HasSubstr("OpSource HLSL 600 %5 \"#define UBER_TYPE(x) x ## Type")); } +TEST_F(AggressiveDCETest, EliminateCopyLogical) { + const std::string before = R"( +; CHECK: [[float32:%\w+]] = OpTypeFloat 32 +; CHECK: [[v4float:%\w+]] = OpTypeVector [[float32]] 4 +; CHECK-NOT: %10 = OpTypeArray [[v4float]] %9 +; CHECK-NOT: %11 = OpTypeStruct %10 %10 +; CHECK-NOT: %22 = OpTypePointer Uniform %16 +; CHECK-NOT: %38 = OpTypePointer Function [[v4float]] +; CHECK-NOT: %43 = OpTypePointer Function %10 +; CHECK-NOT: %44 = OpVariable %42 Function +; CHECK-NOT: %23 = OpAccessChain %22 %19 %21 +; CHECK-NOT: %24 = OpLoad %16 %23 +; CHECK-NOT: %25 = OpCopyLogical %11 %24 +; CHECK-NOT: %46 = OpCompositeExtract %10 %25 0 +; CHECK-NOT: OpStore %44 %46 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" %19 %30 %32 + OpSource GLSL 430 + OpName %4 "main" + OpDecorate %14 ArrayStride 16 + OpDecorate %15 ArrayStride 16 + OpMemberDecorate %16 0 Offset 0 + OpMemberDecorate %16 1 Offset 32 + OpDecorate %17 Block + OpMemberDecorate %17 0 Offset 0 + OpDecorate %19 Binding 0 + OpDecorate %19 DescriptorSet 0 + OpDecorate %28 Block + OpMemberDecorate %28 0 BuiltIn Position + OpMemberDecorate %28 1 BuiltIn PointSize + OpMemberDecorate %28 2 BuiltIn ClipDistance + OpDecorate %32 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeInt 32 0 + %9 = OpConstant %8 2 +%10 = OpTypeArray %7 %9 +%11 = OpTypeStruct %10 %10 +%14 = OpTypeArray %7 %9 +%15 = OpTypeArray %7 %9 +%16 = OpTypeStruct %14 %15 +%17 = OpTypeStruct %16 +%18 = OpTypePointer Uniform %17 +%19 = OpVariable %18 Uniform +%20 = OpTypeInt 32 1 +%21 = OpConstant %20 0 +%22 = OpTypePointer Uniform %16 +%26 = OpConstant %8 1 +%27 = OpTypeArray %6 %26 +%28 = OpTypeStruct %7 %6 %27 +%29 = OpTypePointer Output %28 +%30 = OpVariable %29 Output +%31 = OpTypePointer Input %7 +%32 = OpVariable %31 Input +%33 = OpConstant %8 0 +%34 = OpTypePointer Input %6 +%38 = OpTypePointer Function %7 +%41 = OpTypePointer Output %7 +%43 = OpTypePointer Function %10 +%48 = OpTypePointer Uniform %14 +%49 = OpTypePointer Uniform %7 + %4 = OpFunction %2 None %3 + %5 = OpLabel +%44 = OpVariable %43 Function +%23 = OpAccessChain %22 %19 %21 +%24 = OpLoad %16 %23 +%25 = OpCopyLogical %11 %24 +%46 = OpCompositeExtract %10 %25 0 +%50 = OpAccessChain %48 %19 %21 %33 + OpStore %44 %46 +%35 = OpAccessChain %34 %32 %33 +%36 = OpLoad %6 %35 +%37 = OpConvertFToS %20 %36 +%47 = OpAccessChain %49 %50 %37 +%40 = OpLoad %7 %47 +%42 = OpAccessChain %41 %30 %21 + OpStore %42 %40 + OpReturn + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_UNIVERSAL_1_6); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndMatch(before, true); +} + +TEST_F(AggressiveDCETest, KeepCopyLogical) { + const std::string before = R"( +; CHECK: OpCopyLogical + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %4 "main" %15 %23 %38 + OpExecutionMode %4 LocalSize 32 32 1 + OpSource GLSL 430 + OpName %4 "main" + OpDecorate %10 ArrayStride 16 + OpDecorate %11 ArrayStride 16 + OpMemberDecorate %12 0 Offset 0 + OpMemberDecorate %12 1 Offset 2048 + OpDecorate %13 Block + OpMemberDecorate %13 0 NonReadable + OpMemberDecorate %13 0 Offset 0 + OpDecorate %15 NonReadable + OpDecorate %15 Binding 1 + OpDecorate %15 DescriptorSet 0 + OpDecorate %18 ArrayStride 16 + OpDecorate %19 ArrayStride 16 + OpMemberDecorate %20 0 Offset 0 + OpMemberDecorate %20 1 Offset 2048 + OpDecorate %21 Block + OpMemberDecorate %21 0 NonWritable + OpMemberDecorate %21 0 Offset 0 + OpDecorate %23 NonWritable + OpDecorate %23 Binding 0 + OpDecorate %23 DescriptorSet 0 + OpDecorate %30 ArrayStride 16 + OpDecorate %31 ArrayStride 16 + OpMemberDecorate %32 0 Offset 0 + OpMemberDecorate %32 1 Offset 2048 + OpDecorate %34 ArrayStride 4096 + OpMemberDecorate %35 0 Offset 0 + OpDecorate %36 Block + OpMemberDecorate %36 0 Offset 0 + OpDecorate %38 Binding 0 + OpDecorate %38 DescriptorSet 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeInt 32 0 + %9 = OpConstant %8 128 +%10 = OpTypeArray %7 %9 +%11 = OpTypeArray %7 %9 +%12 = OpTypeStruct %10 %11 +%13 = OpTypeStruct %12 +%14 = OpTypePointer StorageBuffer %13 +%15 = OpVariable %14 StorageBuffer +%16 = OpTypeInt 32 1 +%17 = OpConstant %16 0 +%18 = OpTypeArray %7 %9 +%19 = OpTypeArray %7 %9 +%20 = OpTypeStruct %18 %19 +%21 = OpTypeStruct %20 +%22 = OpTypePointer StorageBuffer %21 +%23 = OpVariable %22 StorageBuffer +%24 = OpTypePointer StorageBuffer %20 +%27 = OpTypePointer StorageBuffer %12 +%30 = OpTypeArray %7 %9 +%31 = OpTypeArray %7 %9 +%32 = OpTypeStruct %30 %31 +%33 = OpConstant %8 8 +%34 = OpTypeArray %32 %33 +%35 = OpTypeStruct %34 +%36 = OpTypeStruct %35 +%37 = OpTypePointer Uniform %36 +%38 = OpVariable %37 Uniform + %4 = OpFunction %2 None %3 + %5 = OpLabel +%25 = OpAccessChain %24 %23 %17 +%26 = OpLoad %20 %25 +%28 = OpAccessChain %27 %15 %17 +%29 = OpCopyLogical %12 %26 + OpStore %28 %29 + OpReturn + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_UNIVERSAL_1_6); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndMatch(before, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/copy_prop_array_test.cpp b/third_party/spirv-tools/test/opt/copy_prop_array_test.cpp index 7d8a325401..c4f1454af1 100644 --- a/third_party/spirv-tools/test/opt/copy_prop_array_test.cpp +++ b/third_party/spirv-tools/test/opt/copy_prop_array_test.cpp @@ -2146,6 +2146,86 @@ OpFunctionEnd SetTargetEnv(SPV_ENV_UNIVERSAL_1_4); SinglePassRunAndMatch(before, true); } + +TEST_F(CopyPropArrayPassTest, PropCopyLogical) { + const std::string before = R"( +; CHECK: [[v4array_ptr:%\w+]] = OpTypePointer Uniform %14 +; CHECK: [[v4_ptr:%\w+]] = OpTypePointer Uniform %7 +; CHECK: [[ac:%\w+]] = OpAccessChain [[v4array_ptr]] %19 %21 %33 +; CHECK: %47 = OpAccessChain [[v4_ptr]] [[ac]] %37 + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %4 "main" %19 %30 %32 + OpSource GLSL 430 + OpName %4 "main" + OpDecorate %14 ArrayStride 16 + OpDecorate %15 ArrayStride 16 + OpMemberDecorate %16 0 Offset 0 + OpMemberDecorate %16 1 Offset 32 + OpDecorate %17 Block + OpMemberDecorate %17 0 Offset 0 + OpDecorate %19 Binding 0 + OpDecorate %19 DescriptorSet 0 + OpDecorate %28 Block + OpMemberDecorate %28 0 BuiltIn Position + OpMemberDecorate %28 1 BuiltIn PointSize + OpMemberDecorate %28 2 BuiltIn ClipDistance + OpDecorate %32 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 4 + %8 = OpTypeInt 32 0 + %9 = OpConstant %8 2 +%10 = OpTypeArray %7 %9 +%11 = OpTypeStruct %10 %10 +%14 = OpTypeArray %7 %9 +%15 = OpTypeArray %7 %9 +%16 = OpTypeStruct %14 %15 +%17 = OpTypeStruct %16 +%18 = OpTypePointer Uniform %17 +%19 = OpVariable %18 Uniform +%20 = OpTypeInt 32 1 +%21 = OpConstant %20 0 +%22 = OpTypePointer Uniform %16 +%26 = OpConstant %8 1 +%27 = OpTypeArray %6 %26 +%28 = OpTypeStruct %7 %6 %27 +%29 = OpTypePointer Output %28 +%30 = OpVariable %29 Output +%31 = OpTypePointer Input %7 +%32 = OpVariable %31 Input +%33 = OpConstant %8 0 +%34 = OpTypePointer Input %6 +%38 = OpTypePointer Function %7 +%41 = OpTypePointer Output %7 +%43 = OpTypePointer Function %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel +%44 = OpVariable %43 Function +%23 = OpAccessChain %22 %19 %21 +%24 = OpLoad %16 %23 +%25 = OpCopyLogical %11 %24 +%46 = OpCompositeExtract %10 %25 0 + OpStore %44 %46 +%35 = OpAccessChain %34 %32 %33 +%36 = OpLoad %6 %35 +%37 = OpConvertFToS %20 %36 +%47 = OpAccessChain %38 %44 %37 +%40 = OpLoad %7 %47 +%42 = OpAccessChain %41 %30 %21 + OpStore %42 %40 + OpReturn + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_UNIVERSAL_1_6); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + SinglePassRunAndMatch(before, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/fold_test.cpp b/third_party/spirv-tools/test/opt/fold_test.cpp index 003c44915e..6d61bb03e8 100644 --- a/third_party/spirv-tools/test/opt/fold_test.cpp +++ b/third_party/spirv-tools/test/opt/fold_test.cpp @@ -208,6 +208,9 @@ TEST_P(IntegerInstructionFoldingTest, Case) { #define VEC4_0_ID 106 #define DVEC4_0_ID 106 #define HALF_0_ID 108 +#define UINT_0_ID 109 +#define INT_NULL_ID 110 +#define UINT_NULL_ID 111 const std::string& Header() { static const std::string header = R"(OpCapability Shader OpCapability Float16 @@ -284,6 +287,7 @@ OpName %main "main" %ubyte_1 = OpConstant %ubyte 1 %byte_n1 = OpConstant %byte -1 %100 = OpConstant %int 0 ; Need a def with an numerical id to define id maps. +%110 = OpConstantNull %int ; Need a def with an numerical id to define id maps. %103 = OpConstant %int 7 ; Need a def with an numerical id to define id maps. %int_0 = OpConstant %int 0 %int_1 = OpConstant %int 1 @@ -314,6 +318,8 @@ OpName %main "main" %long_max = OpConstant %long 9223372036854775807 %ulong_7 = OpConstant %ulong 7 %ulong_4611686018427387904 = OpConstant %ulong 4611686018427387904 +%109 = OpConstant %uint 0 ; Need a def with an numerical id to define id maps. +%111 = OpConstantNull %uint ; Need a def with an numerical id to define id maps. %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %uint_2 = OpConstant %uint 2 @@ -4603,7 +4609,17 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 9: Fold n * 0.0 + // Test case 9: Fold n % 1.0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMod %float %3 %float_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, FLOAT_0_ID), + // Test case 10: Fold n * 0.0 InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4613,7 +4629,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, FLOAT_0_ID), - // Test case 10: Fold 0.0 * n + // Test case 11: Fold 0.0 * n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4623,7 +4639,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, FLOAT_0_ID), - // Test case 11: Fold 0.0 / n + // Test case 12: Fold 0.0 / n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4633,7 +4649,17 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, FLOAT_0_ID), - // Test case 12: Don't fold mix(a, b, 2.0) + // Test case 13: Fold 0.0 % n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_float Function\n" + + "%3 = OpLoad %float %n\n" + + "%2 = OpFMod %float %104 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, FLOAT_0_ID), + // Test case 14: Don't fold mix(a, b, 2.0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4645,7 +4671,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 13: Fold mix(a, b, 0.0) + // Test case 15: Fold mix(a, b, 0.0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4657,7 +4683,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 14: Fold mix(a, b, 1.0) + // Test case 16: Fold mix(a, b, 1.0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4669,7 +4695,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 4), - // Test case 15: Fold vector fadd with null + // Test case 17: Fold vector fadd with null InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4679,7 +4705,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 3, 2), - // Test case 16: Fold vector fadd with null + // Test case 18: Fold vector fadd with null InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4689,7 +4715,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 3, 2), - // Test case 17: Fold vector fsub with null + // Test case 19: Fold vector fsub with null InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4699,7 +4725,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 3, 2), - // Test case 18: Fold 0.0(half) * n + // Test case 20: Fold 0.0(half) * n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4709,7 +4735,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, HALF_0_ID), - // Test case 19: Don't fold 1.0(half) * n + // Test case 21: Don't fold 1.0(half) * n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4719,7 +4745,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 20: Don't fold 1.0 * 1.0 (half) + // Test case 22: Don't fold 1.0 * 1.0 (half) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4727,7 +4753,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 21: Don't fold (0.0, 1.0) * (0.0, 1.0) (half) + // Test case 23: Don't fold (0.0, 1.0) * (0.0, 1.0) (half) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4735,7 +4761,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 22: Don't fold (0.0, 1.0) dotp (0.0, 1.0) (half) + // Test case 24: Don't fold (0.0, 1.0) dotp (0.0, 1.0) (half) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4743,7 +4769,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 23: Don't fold 1.0(half) / 2.0(half) + // Test case 25: Don't fold 1.0(half) / 2.0(half) // We do not have to code to emulate 16-bit float operations. Just make sure we do not crash. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + @@ -4754,7 +4780,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 24: Don't fold OpFNegate for cooperative matrices. + // Test case 26: Don't fold OpFNegate for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4762,7 +4788,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 25: Don't fold OpIAdd for cooperative matrices. + // Test case 27: Don't fold OpIAdd for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4770,7 +4796,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 26: Don't fold OpISub for cooperative matrices. + // Test case 28: Don't fold OpISub for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4778,7 +4804,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 27: Don't fold OpIMul for cooperative matrices. + // Test case 29: Don't fold OpIMul for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4786,7 +4812,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 28: Don't fold OpSDiv for cooperative matrices. + // Test case 30: Don't fold OpSDiv for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -4794,7 +4820,7 @@ INSTANTIATE_TEST_SUITE_P(FloatRedundantFoldingTest, GeneralInstructionFoldingTes "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 29: Don't fold OpMatrixTimesScalar for cooperative matrices. + // Test case 31: Don't fold OpMatrixTimesScalar for cooperative matrices. InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5020,7 +5046,27 @@ INSTANTIATE_TEST_SUITE_P(DoubleVectorRedundantFoldingTest, GeneralInstructionFol "OpReturn\n" + "OpFunctionEnd", 2, DVEC4_0_ID), - // Test case 2: Fold a * vec4(1.0, 1.0, 1.0, 1.0) + // Test case 2: Fold a + vec4(0.0, 0.0, 0.0, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFAdd %v4double %3 %106\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 3: Fold a - vec4(0.0, 0.0, 0.0, 0.0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v4double Function\n" + + "%3 = OpLoad %v4double %n\n" + + "%2 = OpFSub %v4double %3 %106\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 4: Fold a * vec4(1.0, 1.0, 1.0, 1.0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5054,7 +5100,57 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 2: Fold n + 0 + // Test case 2: Fold n | 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpBitwiseOr %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 3: Fold n ^ 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpBitwiseXor %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 4: Fold n >> 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftRightLogical %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 5: Fold n >> 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftRightArithmetic %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 6: Fold n << 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftLeftLogical %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 7: Fold n + 0 InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5064,7 +5160,37 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 3: Fold 0 + n + // Test case 8: Fold n - 0 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpISub %uint %3 %uint_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 9: Fold 0 | n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpBitwiseOr %uint %uint_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 10: Fold 0 ^ n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpBitwiseXor %uint %uint_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 11: Fold 0 + n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5074,7 +5200,7 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 4: Don't fold n + (1,0) + // Test case 12: Don't fold n + (1,0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5084,7 +5210,7 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 5: Don't fold (1,0) + n + // Test case 13: Don't fold (1,0) + n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5094,7 +5220,7 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 6: Fold n + (0,0) + // Test case 14: Fold n + (0,0) InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5104,7 +5230,7 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 7: Fold (0,0) + n + // Test case 15: Fold (0,0) + n InstructionFoldingCase( Header() + "%main = OpFunction %void None %void_func\n" + "%main_lab = OpLabel\n" + @@ -5114,7 +5240,117 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 3), - // Test case 8: Don't fold because of undefined value. Using 4294967295 + // Test case 16: Fold n | (0,0) + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%3 = OpLoad %v2int %n\n" + + "%2 = OpBitwiseOr %v2int %3 %v2int_0_0\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 17: Fold (0,0) | n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_v2int Function\n" + + "%3 = OpLoad %v2int %n\n" + + "%2 = OpBitwiseOr %v2int %v2int_0_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, 3), + // Test case 18: Fold 0 >> n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftRightLogical %uint %109 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_0_ID), + // Test case 19: Fold 0 >> n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftRightArithmetic %uint %109 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_0_ID), + // Test case 20: Fold 0 << n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpShiftLeftLogical %uint %109 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_0_ID), + // Test case 21: Fold 0 / n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %int %n\n" + + "%2 = OpSDiv %int %100 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_0_ID), + // Test case 22: Fold 0 / n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpUDiv %uint %109 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_0_ID), + // Test case 23: Fold 0 % n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpSMod %int %int_0 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_0_ID), + // Test case 24: Fold 0 % n + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpUMod %uint %109 %3\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_0_ID), + // Test case 25: Fold n % 1 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %int %n\n" + + "%2 = OpSMod %int %3 %int_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, INT_NULL_ID), + // Test case 26: Fold n % 1 + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%n = OpVariable %_ptr_uint Function\n" + + "%3 = OpLoad %uint %n\n" + + "%2 = OpUMod %uint %3 %uint_1\n" + + "OpReturn\n" + + "OpFunctionEnd", + 2, UINT_NULL_ID), + // Test case 27: Don't fold because of undefined value. Using 4294967295 // means that entry is undefined. We do not expect it to ever happen, so // not worth folding. InstructionFoldingCase( @@ -5126,7 +5362,7 @@ INSTANTIATE_TEST_SUITE_P(IntegerRedundantFoldingTest, GeneralInstructionFoldingT "OpReturn\n" + "OpFunctionEnd", 2, 0), - // Test case 9: Don't fold because of undefined value. Using 4294967295 + // Test case 28: Don't fold because of undefined value. Using 4294967295 // means that entry is undefined. We do not expect it to ever happen, so // not worth folding. InstructionFoldingCase( diff --git a/third_party/spirv-tools/test/opt/inline_test.cpp b/third_party/spirv-tools/test/opt/inline_test.cpp index ef7ac37d18..272caebc26 100644 --- a/third_party/spirv-tools/test/opt/inline_test.cpp +++ b/third_party/spirv-tools/test/opt/inline_test.cpp @@ -4433,10 +4433,6 @@ TEST_F(InlineTest, DecorateReturnVariableWithAliasedPointer) { OpMemoryModel PhysicalStorageBuffer64 GLSL450 OpEntryPoint GLCompute %1 "main" OpExecutionMode %1 LocalSize 8 8 1 - OpDecorate %_ptr_PhysicalStorageBuffer__struct_5 ArrayStride 8 - OpMemberDecorate %_struct_3 0 Offset 0 - OpMemberDecorate %_struct_3 1 Offset 8 - OpDecorate %_ptr_PhysicalStorageBuffer_int ArrayStride 4 OpMemberDecorate %_struct_5 0 Offset 0 OpMemberDecorate %_struct_5 1 Offset 4 OpDecorate %6 Aliased @@ -4471,6 +4467,86 @@ TEST_F(InlineTest, DecorateReturnVariableWithAliasedPointer) { SinglePassRunAndMatch(text, true); } +TEST_F(InlineTest, DebugDeclareWithAccessChain) { + const std::string text = R"( +; CHECK: [[EmptyStruct:%[\w]+]] = OpTypeStruct %float +; CHECK-DAG: [[Struct:%[\w]+]] = OpTypeStruct [[EmptyStruct]] +; CHECK-DAG: [[PtrType:%[\w]+]] = OpTypePointer Function [[Struct]] +; CHECK-DAG: [[EmptyPtrType:%[\w]+]] = OpTypePointer Function [[EmptyStruct]] + OpCapability Shader + OpExtension "SPV_KHR_non_semantic_info" + OpExtension "SPV_KHR_relaxed_extended_instruction" + %1 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "computeMain" + OpExecutionMode %2 LocalSize 1 1 1 + %3 = OpString "s.hlsl" + %4 = OpString "float" + %5 = OpString "source" + %6 = OpString "a" + %7 = OpString "SomeStruct" + %8 = OpString "SomeStruct.getA" + %9 = OpString "" + %10 = OpString "this" + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_32 = OpConstant %uint 32 + %float = OpTypeFloat 32 + %void = OpTypeVoid + %uint_3 = OpConstant %uint 3 + %uint_1 = OpConstant %uint 1 + %uint_4 = OpConstant %uint 4 + %uint_5 = OpConstant %uint 5 + %uint_11 = OpConstant %uint 11 + %uint_8 = OpConstant %uint 8 + %uint_288 = OpConstant %uint 288 + %25 = OpTypeFunction %void + %_struct_26 = OpTypeStruct %float + %_struct_27 = OpTypeStruct %_struct_26 +%_ptr_Function__struct_27 = OpTypePointer Function %_struct_27 +%_ptr_Function__struct_26 = OpTypePointer Function %_struct_26 +%_ptr_Function_float = OpTypePointer Function %float + %30 = OpTypeFunction %float %_ptr_Function__struct_26 %_ptr_Function_float + %31 = OpUndef %float + %32 = OpExtInst %void %1 DebugTypeBasic %4 %uint_32 %uint_3 %uint_0 + %33 = OpExtInst %void %1 DebugSource %3 %5 + %34 = OpExtInst %void %1 DebugCompilationUnit %uint_1 %uint_4 %33 %uint_5 + %35 = OpExtInst %void %1 DebugTypeMember %6 %32 %33 %uint_3 %uint_11 %uint_0 %uint_32 %uint_3 + %36 = OpExtInstWithForwardRefsKHR %void %1 DebugTypeComposite %7 %uint_1 %33 %uint_1 %uint_8 %34 %7 %uint_32 %uint_3 %35 %37 + %38 = OpExtInst %void %1 DebugTypeFunction %uint_3 %32 %36 + %37 = OpExtInst %void %1 DebugFunction %8 %38 %33 %uint_4 %uint_5 %36 %9 %uint_3 %uint_4 + %39 = OpExtInst %void %1 DebugLocalVariable %10 %36 %33 %uint_4 %uint_5 %37 %uint_288 %uint_1 + %52 = OpExtInst %void %1 DebugLocalVariable %10 %32 %33 %uint_4 %uint_5 %37 %uint_288 %uint_1 + %40 = OpExtInst %void %1 DebugExpression +; CHECK: OpFunction %void None +; CHECK: [[Var:%[\w]+]] = OpVariable [[PtrType]] Function +; CHECK: OpExtInst %void {{%[\w+]+}} DebugDeclare {{%[\w+]+}} [[Var]] {{%[\w+]+}} %int_0 +; CHECK: OpExtInst %void {{%[\w+]+}} DebugDeclare {{%[\w+]+}} [[Var]] {{%[\w+]+}} %int_0 %int_0 + %2 = OpFunction %void None %25 + %41 = OpLabel + %42 = OpVariable %_ptr_Function__struct_27 Function + %43 = OpAccessChain %_ptr_Function__struct_26 %42 %int_0 + %49 = OpAccessChain %_ptr_Function_float %43 %int_0 + %44 = OpFunctionCall %float %45 %43 %49 + OpReturn + OpFunctionEnd +; CHECK: OpFunction %float None + %45 = OpFunction %float None %30 + %46 = OpFunctionParameter %_ptr_Function__struct_26 + %50 = OpFunctionParameter %_ptr_Function_float + %47 = OpLabel + %48 = OpExtInst %void %1 DebugDeclare %39 %46 %40 + %51 = OpExtInst %void %1 DebugDeclare %52 %50 %40 + OpReturnValue %31 + OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_VULKAN_1_2); + SinglePassRunAndMatch(text, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Empty modules diff --git a/third_party/spirv-tools/test/opt/ir_builder.cpp b/third_party/spirv-tools/test/opt/ir_builder.cpp index f0cfc1849b..15d31d9e7f 100644 --- a/third_party/spirv-tools/test/opt/ir_builder.cpp +++ b/third_party/spirv-tools/test/opt/ir_builder.cpp @@ -256,6 +256,38 @@ OpFunctionEnd Match(text, context.get()); } +TEST_F(IRBuilderTest, AddVariable) { + // Use Private beacuse its' enun is 7 which is higher + // than the ID limit. + const std::string text = R"( +; CHECK: [[uint:%\w+]] = OpTypeInt 32 0 +; CHECK: [[ptr:%\w+]] = OpTypePointer Private [[uint]] +; CHECK: [[var:%\w+]] = OpVariable [[ptr]] Private +; CHECK: OpTypeFloat +OpCapability Kernel +OpCapability VectorComputeINTEL +OpCapability Linkage +OpExtension "SPV_INTEL_vector_compute" +OpMemoryModel Logical OpenCL +%1 = OpTypeInt 32 0 +%2 = OpTypePointer Private %1 +%3 = OpTypeFloat 32 +)"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + EXPECT_NE(nullptr, context) << text; + + auto* float_ty = context->get_def_use_mgr()->GetDef(3); + InstructionBuilder builder(context.get(), float_ty); + auto* var = builder.AddVariable(2u, uint32_t(spv::StorageClass::Private)); + EXPECT_NE(nullptr, var); + + context->get_def_use_mgr()->AnalyzeInstDefUse(var); // should not assert + + Match(text, context.get()); +} + TEST_F(IRBuilderTest, AddCompositeConstruct) { const std::string text = R"( ; CHECK: [[uint:%\w+]] = OpTypeInt diff --git a/third_party/spirv-tools/test/opt/optimizer_test.cpp b/third_party/spirv-tools/test/opt/optimizer_test.cpp index 0171c09b9c..49d4f8f602 100644 --- a/third_party/spirv-tools/test/opt/optimizer_test.cpp +++ b/third_party/spirv-tools/test/opt/optimizer_test.cpp @@ -388,6 +388,124 @@ OpFunctionEnd << "Was expecting the result id of DebugScope to have been changed."; } +TEST(Optimizer, CheckDefaultPerformancePassesLargeStructScalarization) { + std::string start = R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %4 "main" %46 %48 +OpSource GLSL 430 +OpName %4 "main" +OpDecorate %44 Block +OpMemberDecorate %44 0 BuiltIn Position +OpMemberDecorate %44 1 BuiltIn PointSize +OpMemberDecorate %44 2 BuiltIn ClipDistance +OpDecorate %48 Location 0 +%2 = OpTypeVoid +%3 = OpTypeFunction %2 +%6 = OpTypeFloat 32 +%7 = OpTypeVector %6 4 +%8 = OpTypePointer Function %7 +%9 = OpTypeStruct %7)"; + + // add 200 float members to the struct + for (int i = 0; i < 200; i++) { + start += " %6"; + } + + start += R"( +%10 = OpTypeFunction %9 %8 +%14 = OpTypeFunction %6 %9 +%18 = OpTypePointer Function %9 +%20 = OpTypeInt 32 1 +%21 = OpConstant %20 0 +%24 = OpConstant %20 1 +%25 = OpTypeInt 32 0 +%26 = OpConstant %25 1 +%27 = OpTypePointer Function %6 +%43 = OpTypeArray %6 %26 +%44 = OpTypeStruct %7 %6 %43 +%45 = OpTypePointer Output %44 +%46 = OpVariable %45 Output +%47 = OpTypePointer Input %7 +%48 = OpVariable %47 Input +%54 = OpTypePointer Output %7 +%4 = OpFunction %2 None %3 +%5 = OpLabel +%49 = OpVariable %8 Function +%50 = OpLoad %7 %48 +OpStore %49 %50 +%51 = OpFunctionCall %9 %12 %49 +%52 = OpFunctionCall %6 %16 %51 +%53 = OpCompositeConstruct %7 %52 %52 %52 %52 +%55 = OpAccessChain %54 %46 %21 +OpStore %55 %53 +OpReturn +OpFunctionEnd +%12 = OpFunction %9 None %10 +%11 = OpFunctionParameter %8 +%13 = OpLabel +%19 = OpVariable %18 Function +%22 = OpLoad %7 %11 +%23 = OpAccessChain %8 %19 %21 +OpStore %23 %22 +%28 = OpAccessChain %27 %11 %26 +%29 = OpLoad %6 %28 +%30 = OpConvertFToS %20 %29 +%31 = OpAccessChain %27 %19 %21 %30 +%32 = OpLoad %6 %31 +%33 = OpAccessChain %27 %19 %24 +OpStore %33 %32 +%34 = OpLoad %9 %19 +OpReturnValue %34 +OpFunctionEnd +%16 = OpFunction %6 None %14 +%15 = OpFunctionParameter %9 +%17 = OpLabel +%37 = OpCompositeExtract %6 %15 1 +%38 = OpConvertFToS %20 %37 +%39 = OpCompositeExtract %7 %15 0 +%40 = OpVectorExtractDynamic %6 %39 %38 +OpReturnValue %40 +OpFunctionEnd)"; + + std::vector binary; + SpirvTools tools(SPV_ENV_VULKAN_1_3); + tools.Assemble(start, &binary, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + std::string test_disassembly; + std::string default_disassembly; + + { + Optimizer opt(SPV_ENV_VULKAN_1_3); + opt.RegisterPerformancePasses(); + + std::vector optimized; + ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized)) + << start << "\n"; + + tools.Disassemble(optimized.data(), optimized.size(), &default_disassembly, + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } + + { + // default passes should not benefit from additional scalar replacement + Optimizer opt(SPV_ENV_VULKAN_1_3); + opt.RegisterPerformancePasses() + .RegisterPass(CreateScalarReplacementPass(201)) + .RegisterPass(CreateAggressiveDCEPass()); + + std::vector optimized; + ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized)) + << start << "\n"; + + tools.Disassemble(optimized.data(), optimized.size(), &test_disassembly, + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } + + EXPECT_EQ(test_disassembly, default_disassembly); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/pass_fixture.h b/third_party/spirv-tools/test/opt/pass_fixture.h index e52082188c..32e529608d 100644 --- a/third_party/spirv-tools/test/opt/pass_fixture.h +++ b/third_party/spirv-tools/test/opt/pass_fixture.h @@ -35,6 +35,26 @@ namespace spvtools { namespace opt { +inline std::ostream& operator<<(std::ostream& os, + const effcee::Result::Status ers) { + switch (ers) { + case effcee::Result::Status::Ok: + return os << "effcee::Result::Status::Ok"; + case effcee::Result::Status::Fail: + return os << "effcee::Result::Status::Fail"; + case effcee::Result::Status::BadOption: + return os << "effcee::Result::Status::BadOption"; + case effcee::Result::Status::NoRules: + return os << "effcee::Result::Status::NoRules"; + case effcee::Result::Status::BadRule: + return os << "effcee::Result::Status::BadRule"; + default: + break; + } + return os << "(invalid effcee::Result::Status " << static_cast(ers) + << ")"; +} + // Template class for testing passes. It contains some handy utility methods for // running passes and checking results. // diff --git a/third_party/spirv-tools/test/opt/pass_utils.h b/third_party/spirv-tools/test/opt/pass_utils.h index 8968f8a64b..ddf3b9f38e 100644 --- a/third_party/spirv-tools/test/opt/pass_utils.h +++ b/third_party/spirv-tools/test/opt/pass_utils.h @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -24,10 +25,25 @@ #include "gtest/gtest.h" #include "include/spirv-tools/libspirv.h" #include "include/spirv-tools/libspirv.hpp" +#include "source/opt/pass.h" namespace spvtools { namespace opt { +inline std::ostream& operator<<(std::ostream& os, const Pass::Status ps) { + switch (ps) { + case Pass::Status::Failure: + return os << "Pass::Status::Failure"; + case Pass::Status::SuccessWithChange: + return os << "Pass::Status::SuccessWithChange"; + case Pass::Status::SuccessWithoutChange: + return os << "Pass::Status::SuccessWithoutChange"; + default: + break; + } + return os << "(invalid Pass::Status " << static_cast(ps) << ")"; +} + struct Message { spv_message_level_t level; const char* source_file; diff --git a/third_party/spirv-tools/test/opt/resolve_binding_conflicts_pass_test.cpp b/third_party/spirv-tools/test/opt/resolve_binding_conflicts_pass_test.cpp new file mode 100644 index 0000000000..31378b9dbc --- /dev/null +++ b/third_party/spirv-tools/test/opt/resolve_binding_conflicts_pass_test.cpp @@ -0,0 +1,864 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "spirv-tools/optimizer.hpp" +#include "test/opt/pass_fixture.h" +#include "test/opt/pass_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +struct ResolveBindingConflictsTest : public PassTest<::testing::Test> { + virtual void SetUp() override { + SetTargetEnv(SPV_ENV_VULKAN_1_1); // allow storage buffer storage class + // without extension. + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } +}; + +using StringList = std::vector; +std::string EntryPointDecls(const StringList& names) { + std::ostringstream os; + for (auto& name : names) { + os << " OpEntryPoint GLCompute %" + name + " \"" + name + + "\"\n"; + } + for (auto& name : names) { + os << " OpExecutionMode %" + name + " LocalSize 1 1 1\n"; + } + for (auto& name : names) { + os << " OpName %" + name + " \"" + name + "\"\n"; + } + return os.str(); +} + +std::string Preamble(const StringList& names = {"main"}) { + return R"( OpCapability Shader + OpMemoryModel Logical GLSL450 +)" + EntryPointDecls(names) + + R"( OpName %voidfn "voidfn" + OpName %s_ty "s_ty" + OpName %i_ty "i_ty" + OpName %si_ty "si_ty" + OpName %p_s_ty "p_s_ty" + OpName %p_i_ty "p_i_ty" + OpName %p_si_ty "p_si_ty" + OpName %st_ty "st_ty" + OpName %pu_st_ty "pu_st_ty" + OpName %pb_st_ty "pb_st_ty" +)"; +} + +std::string BasicTypes() { + return R"( OpDecorate %st_ty Block + OpMemberDecorate %st_ty 0 Offset 0 + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_3 = OpConstant %uint 3 + %void = OpTypeVoid + %voidfn = OpTypeFunction %void + %s_ty = OpTypeSampler + %i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %si_ty = OpTypeSampledImage %i_ty + %p_i_ty = OpTypePointer UniformConstant %i_ty + %p_s_ty = OpTypePointer UniformConstant %s_ty + %p_si_ty = OpTypePointer UniformConstant %si_ty + %st_ty = OpTypeStruct %uint + %pu_st_ty = OpTypePointer Uniform %st_ty + %pb_st_ty = OpTypePointer StorageBuffer %st_ty +)"; +} +std::string NoCheck() { return "; CHECK-NOT: nothing to see"; } + +TEST_F(ResolveBindingConflictsTest, NoBindings_NoChange) { + const std::string kTest = Preamble() + BasicTypes() + + R"( %main = OpFunction %void None %voidfn + %100 = OpLabel + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange); + EXPECT_EQ(kTest, disasm); +} + +TEST_F(ResolveBindingConflictsTest, NoConflict_UnusedVars_NoChange) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 0 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 0 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 0 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 0 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 0 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 0 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 0 + +)" + BasicTypes() + R"( + + ; Unused variables + + %100 = OpVariable %p_i_ty UniformConstant ; image + %101 = OpVariable %p_s_ty UniformConstant ; sampler + %102 = OpVariable %pu_st_ty Uniform ; UBO + %103 = OpVariable %pb_st_ty StorageBuffer ; SSBO + %104 = OpVariable %p_si_ty UniformConstant ; combined sampled image + + %main = OpFunction %void None %voidfn + %10 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, NoConflict_UsedVars_NoChange) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 1 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 2 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 3 + OpDecorate %110 DescriptorSet 1 + OpDecorate %110 Binding 0 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 0 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 1 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %110 DescriptorSet 1 + ; CHECK: OpDecorate %110 Binding 0 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_i_ty UniformConstant ; image + %101 = OpVariable %p_s_ty UniformConstant ; sampler + %102 = OpVariable %pu_st_ty Uniform ; UBO + %103 = OpVariable %pb_st_ty StorageBuffer ; SSBO + %110 = OpVariable %p_si_ty UniformConstant ; combined sampled image + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_i_ty %100 + %12 = OpCopyObject %p_s_ty %101 + %13 = OpCopyObject %pu_st_ty %102 + %14 = OpCopyObject %pb_st_ty %103 + %15 = OpCopyObject %p_si_ty %110 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + OneEntryPoint_SamplerFirstConflict_Resolves) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + + ; The sampler's binding number is incremented, even when listed first. + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; sampler listed first + %101 = OpVariable %p_i_ty UniformConstant + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_s_ty %100 + %12 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + OneEntryPoint_SamplerSecondConflict_Resolves) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + + ; The sampler's binding number is incremented, even when listed second. + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 0 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 1 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_i_ty UniformConstant + %101 = OpVariable %p_s_ty UniformConstant ; sampler listed second + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_i_ty %100 + %12 = OpCopyObject %p_s_ty %101 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, OneEntryPoint_Conflict_Ripples) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 2 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 3 + + ; The sampler's binding number is incremented, and later + ; bindings move out of the way. + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first + %101 = OpVariable %p_i_ty UniformConstant + %102 = OpVariable %pu_st_ty Uniform + %103 = OpVariable %pb_st_ty StorageBuffer + %104 = OpVariable %p_si_ty UniformConstant + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_s_ty %100 + %12 = OpCopyObject %p_i_ty %101 + %13 = OpCopyObject %pu_st_ty %102 + %14 = OpCopyObject %pb_st_ty %103 + %15 = OpCopyObject %p_si_ty %104 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + OneEntryPoint_Conflict_RippleStopsAtFirstHole) { + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + ; Leave a hole at (0, 2) + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 3 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 4 + + ; There was a hole at binding 2. The ripple stops there. + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first + %101 = OpVariable %p_i_ty UniformConstant + %102 = OpVariable %pu_st_ty Uniform + %103 = OpVariable %pb_st_ty StorageBuffer + %104 = OpVariable %p_si_ty UniformConstant + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_s_ty %100 + %12 = OpCopyObject %p_i_ty %101 + %13 = OpCopyObject %pu_st_ty %102 + %14 = OpCopyObject %pb_st_ty %103 + %15 = OpCopyObject %p_si_ty %104 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, OneEntryPoint_MultiConflict_Resolves) { + const std::string kTest = Preamble() + + R"( + ; Two conflicts: at Bindings 0, and 1 + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 1 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 2 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; sampler first + %101 = OpVariable %p_i_ty UniformConstant + %102 = OpVariable %p_i_ty UniformConstant + %103 = OpVariable %p_s_ty UniformConstant ; sampler second + %104 = OpVariable %pu_st_ty Uniform + + %main = OpFunction %void None %voidfn + %10 = OpLabel + %11 = OpCopyObject %p_s_ty %100 + %12 = OpCopyObject %p_i_ty %101 + %13 = OpCopyObject %p_i_ty %102 + %14 = OpCopyObject %p_s_ty %103 + %15 = OpCopyObject %pu_st_ty %104 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + OneEntryPoint_MultiConflict_ComplexCallGraph_Resolves) { + // Check that uses are seen even when used at various points in a complex call + // graph. + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 1 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 2 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; used in %200 + %101 = OpVariable %p_i_ty UniformConstant ; used in %300, %400 + %102 = OpVariable %p_i_ty UniformConstant ; used in %500 + %103 = OpVariable %p_s_ty UniformConstant ; used in %400 twice + %104 = OpVariable %pu_st_ty Uniform ; used in %600 + + %200 = OpFunction %void None %voidfn + %201 = OpLabel + %202 = OpCopyObject %p_s_ty %100 + OpReturn + OpFunctionEnd + + %300 = OpFunction %void None %voidfn + %301 = OpLabel + %302 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd + + %400 = OpFunction %void None %voidfn + %401 = OpLabel + %402 = OpFunctionCall %void %200 + %403 = OpCopyObject %p_s_ty %103 + %404 = OpCopyObject %p_i_ty %101 + %405 = OpCopyObject %p_s_ty %103 + %406 = OpFunctionCall %void %300 + OpReturn + OpFunctionEnd + + %500 = OpFunction %void None %voidfn + %501 = OpLabel + %502 = OpFunctionCall %void %400 + %503 = OpCopyObject %p_i_ty %102 + %504 = OpFunctionCall %void %300 + OpReturn + OpFunctionEnd + + %600 = OpFunction %void None %voidfn + %601 = OpLabel + %602 = OpFunctionCall %void %300 + %603 = OpFunctionCall %void %500 + %604 = OpCopyObject %pu_st_ty %104 + OpReturn + OpFunctionEnd + + %main = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpFunctionCall %void %600 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + MultiEntryPoint_DuplicatConflicts_ResolvesOnlyOnce) { + // Before: + // + // Binding: 0 1 + // Alpha: %100,%101 + // Beta: %100,%101 + // + // After: + // + // Binding: 0 1 + // Alpha: %101 %100 + // Beta: %101 %100 + const std::string kTest = Preamble({"alpha", "beta"}) + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant + %101 = OpVariable %p_i_ty UniformConstant + + %alpha = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpCopyObject %p_s_ty %100 + %1002 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd + + %beta = OpFunction %void None %voidfn + %2000 = OpLabel + %2001 = OpCopyObject %p_s_ty %100 + %2002 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + MultiEntryPoint_IndependentConflicts_Resolves) { + // Before: + // + // Binding: 0 1 + // Alpha: %100,%101 + // Beta: %102,%103 + // + // After: + // + // Binding: 0 1 + // Alpha: %101 %100 + // Beta: %102 %103 + const std::string kTest = Preamble({"alpha", "beta"}) + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 0 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 0 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 0 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 1 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant + %101 = OpVariable %p_i_ty UniformConstant + %102 = OpVariable %p_i_ty UniformConstant + %103 = OpVariable %p_s_ty UniformConstant + + %alpha = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpCopyObject %p_s_ty %100 + %1002 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd + + %beta = OpFunction %void None %voidfn + %2000 = OpLabel + %2001 = OpCopyObject %p_i_ty %102 + %2002 = OpCopyObject %p_s_ty %103 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + MultiEntryPoint_SameVarConflictsAcrossMultiEntryPoints_Resolves) { + // A sampler variable is bumped, causing potential conflicts in other shaders. + // + // Before: + // + // Binding: 0 1 2 + // Alpha: %100,%101 + // Beta: %100 %102 + // Gamma: %100 %103 + // + // After: + // + // Binding: 0 1 2 + // Alpha: %101 %100 + // Beta: %100 %102 + // Gamma: %100 %103 + // + const std::string kTest = Preamble({"alpha", "beta", "gamma"}) + + R"( + OpDecorate %100 DescriptorSet 0 ; The sampler + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 2 + + ; bumped once + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + + ; pushed back from bump of %100 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + + ; does not need to be bumped + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 2 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta, gamma + %101 = OpVariable %p_i_ty UniformConstant ; used in alpha + %102 = OpVariable %pu_st_ty Uniform ; used in beta + %103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma + + %alpha = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpCopyObject %p_s_ty %100 + %1002 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd + + %beta = OpFunction %void None %voidfn + %2000 = OpLabel + %2001 = OpCopyObject %p_s_ty %100 + %2002 = OpCopyObject %pu_st_ty %102 + OpReturn + OpFunctionEnd + + %gamma = OpFunction %void None %voidfn + %3000 = OpLabel + %3001 = OpCopyObject %p_s_ty %100 + %3002 = OpCopyObject %pb_st_ty %103 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, MultiEntryPoint_ConflictCascade_Resolves) { + // Before: + // + // Binding: 0 1 2 3 + // Alpha: %100,%101 + // Beta: %100 %102 + // Gamma: %102 %103 + // Delta: %103 %104 + // + // After: + // + // Binding: 0 1 2 3 4 + // Alpha: %101 %100 + // Beta: %100 %102 + // Gamma: %102 %103 + // Delta: %103 %104 + // + const std::string kTest = Preamble({"alpha", "beta", "gamma", "delta"}) + + R"( + OpDecorate %100 DescriptorSet 0 ; The sampler + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 2 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 3 + + ; %100 is bumped once: + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 1 + + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 0 + + ; pushed back from bump of %100 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + + ; pushed back from bump of %102 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + + ; pushed back from bump of %103 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta + %101 = OpVariable %p_i_ty UniformConstant ; used in alpha + %102 = OpVariable %pu_st_ty Uniform ; used in beta, gamma + %103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma, delta + %104 = OpVariable %p_si_ty UniformConstant ; used delta + + %alpha = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpCopyObject %p_s_ty %100 + %1002 = OpCopyObject %p_i_ty %101 + OpReturn + OpFunctionEnd + + %beta = OpFunction %void None %voidfn + %2000 = OpLabel + %2001 = OpCopyObject %p_s_ty %100 + %2002 = OpCopyObject %pu_st_ty %102 + OpReturn + OpFunctionEnd + + %gamma = OpFunction %void None %voidfn + %3000 = OpLabel + %3001 = OpCopyObject %pu_st_ty %102 + %3002 = OpCopyObject %pb_st_ty %103 + OpReturn + OpFunctionEnd + + %delta = OpFunction %void None %voidfn + %4000 = OpLabel + %4001 = OpCopyObject %pb_st_ty %103 + %4002 = OpCopyObject %p_si_ty %104 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(ResolveBindingConflictsTest, + MultiEntryPoint_ConflictCascade_RevisitEntryPoint) { + // Prove that the settling algorithm knows to revisit entry points that + // already had all their own conflicts resolved. + // + // Before: + // + // Binding: 0 1 2 3 4 + // Alpha: %100,%101 %103 %104 %105 + // Beta: %101 %102 %103 %105 + // + // After: + // + // Binding: 0 1 2 3 4 5 + // Alpha: %100 %101 %103 %104 %105 + // Beta: %101 %102 %103 %105 + const std::string kTest = Preamble({"alpha", "beta"}) + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %101 DescriptorSet 0 ; the sampler + OpDecorate %101 Binding 0 + OpDecorate %102 DescriptorSet 0 + OpDecorate %102 Binding 1 + OpDecorate %103 DescriptorSet 0 + OpDecorate %103 Binding 2 + OpDecorate %104 DescriptorSet 0 + OpDecorate %104 Binding 3 + OpDecorate %105 DescriptorSet 0 + OpDecorate %105 Binding 4 + + ; CHECK: OpDecorate %100 DescriptorSet 0 + ; CHECK: OpDecorate %100 Binding 0 + ; CHECK: OpDecorate %101 DescriptorSet 0 + ; CHECK: OpDecorate %101 Binding 1 + ; CHECK: OpDecorate %102 DescriptorSet 0 + ; CHECK: OpDecorate %102 Binding 2 + ; CHECK: OpDecorate %103 DescriptorSet 0 + ; CHECK: OpDecorate %103 Binding 3 + ; CHECK: OpDecorate %104 DescriptorSet 0 + ; CHECK: OpDecorate %104 Binding 4 + ; CHECK: OpDecorate %105 DescriptorSet 0 + ; CHECK: OpDecorate %105 Binding 5 + +)" + BasicTypes() + R"( + + %100 = OpVariable %p_i_ty UniformConstant + %101 = OpVariable %p_s_ty UniformConstant + %102 = OpVariable %pu_st_ty Uniform + %103 = OpVariable %pb_st_ty StorageBuffer + %104 = OpVariable %p_si_ty UniformConstant + %105 = OpVariable %p_s_ty UniformConstant + + %alpha = OpFunction %void None %voidfn + %1000 = OpLabel + %1001 = OpCopyObject %p_i_ty %100 + %1002 = OpCopyObject %p_s_ty %101 + %1003 = OpCopyObject %pb_st_ty %103 + %1004 = OpCopyObject %p_si_ty %104 + %1005 = OpCopyObject %p_s_ty %105 + OpReturn + OpFunctionEnd + + %beta = OpFunction %void None %voidfn + %2000 = OpLabel + %2001 = OpCopyObject %p_s_ty %101 + %2002 = OpCopyObject %pu_st_ty %102 + %2003 = OpCopyObject %pb_st_ty %103 + %2004 = OpCopyObject %p_s_ty %105 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +} // namespace +} // namespace opt +} // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/scalar_replacement_test.cpp b/third_party/spirv-tools/test/opt/scalar_replacement_test.cpp index 0ba285bb62..01fa7217f5 100644 --- a/third_party/spirv-tools/test/opt/scalar_replacement_test.cpp +++ b/third_party/spirv-tools/test/opt/scalar_replacement_test.cpp @@ -27,7 +27,7 @@ using ScalarReplacementPassName = ::testing::Test; TEST_F(ScalarReplacementPassName, Default) { auto srp = ScalarReplacementPass(); - EXPECT_STREQ(srp.name(), "scalar-replacement=100"); + EXPECT_STREQ(srp.name(), "scalar-replacement=0"); } TEST_F(ScalarReplacementPassName, Large) { diff --git a/third_party/spirv-tools/test/opt/split_combined_image_sampler_pass_test.cpp b/third_party/spirv-tools/test/opt/split_combined_image_sampler_pass_test.cpp new file mode 100644 index 0000000000..2ffffa5ad6 --- /dev/null +++ b/third_party/spirv-tools/test/opt/split_combined_image_sampler_pass_test.cpp @@ -0,0 +1,1868 @@ +// Copyright (c) 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "spirv-tools/optimizer.hpp" +#include "test/opt/pass_fixture.h" +#include "test/opt/pass_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +struct SplitCombinedImageSamplerPassTest : public PassTest<::testing::Test> { + virtual void SetUp() override { + SetTargetEnv(SPV_ENV_VULKAN_1_0); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } +}; + +struct TypeCase { + const char* glsl_type; + const char* image_type_decl; +}; +std::ostream& operator<<(std::ostream& os, const TypeCase& tc) { + os << tc.glsl_type; + return os; +} + +struct SplitCombinedImageSamplerPassTypeCaseTest + : public PassTest<::testing::TestWithParam> { + virtual void SetUp() override { + SetTargetEnv(SPV_ENV_VULKAN_1_0); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } +}; + +std::vector ImageTypeCases() { + return std::vector{ + {"sampler2D", "OpTypeImage %float 2D 0 0 0 1 Unknown"}, + {"sampler2DShadow", "OpTypeImage %float 2D 1 0 0 1 Unknown"}, + {"sampler2DArray", "OpTypeImage %float 2D 0 1 0 1 Unknown"}, + {"sampler2DArrayShadow", "OpTypeImage %float 2D 1 1 0 1 Unknown"}, + {"sampler2DMS", "OpTypeImage %float 2D 0 0 1 1 Unknown"}, + {"sampler2DMSArray", "OpTypeImage %float 2D 0 1 1 1 Unknown"}, + {"sampler3D", "OpTypeImage %float 3D 0 0 0 1 Unknown"}, + {"samplerCube", "OpTypeImage %float Cube 0 0 0 1 Unknown"}, + {"samplerCubeShadow", "OpTypeImage %float Cube 1 0 0 1 Unknown"}, + {"samplerCubeArray", "OpTypeImage %float Cube 0 1 0 1 Unknown"}, + {"samplerCubeArrayShadow", "OpTypeImage %float Cube 1 1 0 1 Unknown"}, + {"isampler2D", "OpTypeImage %int 2D 0 0 0 1 Unknown"}, + {"isampler2DShadow", "OpTypeImage %int 2D 1 0 0 1 Unknown"}, + {"isampler2DArray", "OpTypeImage %int 2D 0 1 0 1 Unknown"}, + {"isampler2DArrayShadow", "OpTypeImage %int 2D 1 1 0 1 Unknown"}, + {"isampler2DMS", "OpTypeImage %int 2D 0 0 1 1 Unknown"}, + {"isampler2DMSArray", "OpTypeImage %int 2D 0 1 1 1 Unknown"}, + {"isampler3D", "OpTypeImage %int 3D 0 0 0 1 Unknown"}, + {"isamplerCube", "OpTypeImage %int Cube 0 0 0 1 Unknown"}, + {"isamplerCubeShadow", "OpTypeImage %int Cube 1 0 0 1 Unknown"}, + {"isamplerCubeArray", "OpTypeImage %int Cube 0 1 0 1 Unknown"}, + {"isamplerCubeArrayShadow", "OpTypeImage %int Cube 1 1 0 1 Unknown"}, + {"usampler2D", "OpTypeImage %uint 2D 0 0 0 1 Unknown"}, + {"usampler2DShadow", "OpTypeImage %uint 2D 1 0 0 1 Unknown"}, + {"usampler2DArray", "OpTypeImage %uint 2D 0 1 0 1 Unknown"}, + {"usampler2DArrayShadow", "OpTypeImage %uint 2D 1 1 0 1 Unknown"}, + {"usampler2DMS", "OpTypeImage %uint 2D 0 0 1 1 Unknown"}, + {"usampler2DMSArray", "OpTypeImage %uint 2D 0 1 1 1 Unknown"}, + {"usampler3D", "OpTypeImage %uint 3D 0 0 0 1 Unknown"}, + {"usamplerCube", "OpTypeImage %uint Cube 0 0 0 1 Unknown"}, + {"usamplerCubeShadow", "OpTypeImage %uint Cube 1 0 0 1 Unknown"}, + {"usamplerCubeArray", "OpTypeImage %uint Cube 0 1 0 1 Unknown"}, + {"usamplerCubeArrayShadow", "OpTypeImage %uint Cube 1 1 0 1 Unknown"}, + }; +} + +std::string Preamble(const std::string shader_interface = "") { + return R"( OpCapability Shader + OpCapability RuntimeDescriptorArray + OpExtension "SPV_EXT_descriptor_indexing" + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %main "main")" + + shader_interface + R"( + OpExecutionMode %main LocalSize 1 1 1 + OpName %main "main" + OpName %main_0 "main_0" + OpName %voidfn "voidfn" +)"; +} + +std::string PreambleFragment(const std::string shader_interface = "") { + return R"( OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main")" + + shader_interface + R"( + OpExecutionMode %main OriginUpperLeft + OpName %main "main" + OpName %main_0 "main_0" + OpName %voidfn "voidfn" +)"; +} + +std::string BasicTypes() { + return R"( %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %int = OpTypeInt 32 1 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_3 = OpConstant %uint 3 + %float_0 = OpConstant %float 0 + %v2float = OpTypeVector %float 2 + %v3float = OpTypeVector %float 3 + %v4float = OpTypeVector %float 4 + %13 = OpConstantNull %v2float + %14 = OpConstantNull %v3float + %15 = OpConstantNull %v4float + %void = OpTypeVoid + %voidfn = OpTypeFunction %void +)"; +} +std::string Main() { + return R"( + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd +)"; +} +std::string NoCheck() { return "; CHECK-NOT: nothing to see"; } + +TEST_F(SplitCombinedImageSamplerPassTest, SamplerOnly_NoChange) { + const std::string kTest = Preamble() + + R"( OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 +)" + BasicTypes() + R"( %10 = OpTypeSampler +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %100 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %6 = OpLoad %10 %100 + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) + << "status" << kTest << "\n -> \n" + << disasm; + EXPECT_EQ(disasm, kTest) << "disasm"; +} + +TEST_F(SplitCombinedImageSamplerPassTest, ImageOnly_NoChange) { + const std::string kTest = Preamble() + + R"( OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 +)" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown +%_ptr_UniformConstant_10 = OpTypePointer UniformConstant %10 + %100 = OpVariable %_ptr_UniformConstant_10 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %6 = OpLoad %10 %100 + OpReturn + OpFunctionEnd +)"; + + SCOPED_TRACE("image only"); + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange); + EXPECT_EQ(disasm, kTest); +} + +TEST_F(SplitCombinedImageSamplerPassTest, PtrSampledImageOnly_DeletesPtrType) { + const std::string kTest = Preamble() + BasicTypes() + R"( + ; CHECK: OpCapability Shader + ; CHECK-NOT: OpTypePointer UniformConstant + ; CHECK: OpFunction %void + %100 = OpTypeImage %float 2D 0 0 0 1 Unknown + %101 = OpTypeSampledImage %100 + %102 = OpTypePointer UniformConstant %101 + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + PtrArraySampledImageOnly_DeletesPtrType) { + const std::string kTest = Preamble() + BasicTypes() + R"( + ; CHECK: OpCapability Shader + ; CHECK-NOT: OpTypePointer UniformConstant + ; CHECK: OpFunction %void + %100 = OpTypeImage %float 2D 0 0 0 1 Unknown + %101 = OpTypeSampledImage %100 + %103 = OpTypeArray %101 %uint_1 + %104 = OpTypePointer UniformConstant %103 + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + PtrRtArraySampledImageOnly_DeletesPtrType) { + const std::string kTest = Preamble() + BasicTypes() + R"( + ; CHECK: OpCapability Shader + ; CHECK-NOT: OpTypePointer UniformConstant + ; CHECK: OpFunction %void + %100 = OpTypeImage %float 2D 0 0 0 1 Unknown + %101 = OpTypeSampledImage %100 + %103 = OpTypeRuntimeArray %101 + %104 = OpTypePointer UniformConstant %103 + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest + NoCheck(), /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << "status"; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + Combined_NoSampler_CreatedBeforeSampledImage) { + // No OpTypeSampler to begin with. + const std::string kTest = Preamble() + + R"( OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + + ; A sampler type is created and placed at the start of types. + ; CHECK: OpDecorate %{{\d+}} Binding 0 + ; CHECK: OpDecorate %{{\d+}} Binding 0 + ; CHECK-NOT: TypeSampledImage + ; CHECK: TypeSampler + ; CHECK: TypeSampledImage + +)" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + + %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %6 = OpLoad %11 %100 + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, Combined_SynthesizeVarNames) { + // Also tests binding info is copied to both variables. + const std::string kTest = Preamble() + + R"( + OpName %orig_var "orig_var" + OpDecorate %orig_var DescriptorSet 0 + OpDecorate %orig_var Binding 0 + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK: OpCapability + ; The original name is deleted + ; CHECK-NOT: OpName %orig_var " + ; CHECK: OpName %orig_var_image "orig_var_image" + ; CHECK: OpName %orig_var_sampler "orig_var_sampler" + ; CHECK-NOT: OpName %orig_var " + + ; CHECK: OpDecorate %orig_var_image DescriptorSet 0 + ; CHECK: OpDecorate %orig_var_sampler DescriptorSet 0 + ; CHECK: OpDecorate %orig_var_image Binding 0 + ; CHECK: OpDecorate %orig_var_sampler Binding 0 + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + + ; CHECK-NOT: %orig_var = OpVariable + ; CHECK-DAG: %orig_var_sampler = OpVariable %[[sampler_ptr_ty]] UniformConstant + ; CHECK-DAG: %orig_var_image = OpVariable %[[image_ptr_ty]] UniformConstant + ; CHECK: = OpFunction + +)" + BasicTypes() + R"( + %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + + %orig_var = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %101 = OpLoad %11 %orig_var + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, Combined_RemapLoad) { + // Also tests binding info is copied to both variables. + const std::string kTest = Preamble() + + R"( + OpName %combined "combined" + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + + ; CHECK: OpName + ; CHECK-NOT: OpDecorate %100 + ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[image_var]] Binding 0 + ; CHECK: OpDecorate %[[sampler_var]] Binding 0 + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK-NOT: %100 = OpVariable + ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant + ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant + ; CHECK: = OpFunction + + ; The load of the combined image+sampler is replaced by a two loads, then + ; a combination operation. + ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] + ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] + ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] + +)" + BasicTypes() + + " %10 = " + GetParam().image_type_decl + R"( + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + + %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %combined = OpLoad %11 %100 + + ; Uses of the combined image sampler are preserved. + ; CHECK: OpCopyObject %11 %combined + + %7 = OpCopyObject %11 %combined + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, + Combined_RemapLoad_RelaxedPrecisionCopied) { + // All decorations are copied. In this case, RelaxedPrecision + const std::string kTest = Preamble() + + R"( + OpName %combined "combined" + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + OpDecorate %100 RelaxedPrecision + + ; CHECK: OpName + ; CHECK-NOT: OpDecorate %100 + ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[image_var]] Binding 0 + ; CHECK: OpDecorate %[[sampler_var]] Binding 0 + ; CHECK: OpDecorate %[[image_var:\d+]] RelaxedPrecision + ; CHECK: OpDecorate %[[sampler_var:\d+]] RelaxedPrecision + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK-NOT: %100 = OpVariable + ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant + ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant + ; CHECK: = OpFunction + + ; The load of the combined image+sampler is replaced by a two loads, then + ; a combination operation. + ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] + ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] + ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] + + %bool = OpTypeBool ; location marker +)" + BasicTypes() + + " %10 = " + GetParam().image_type_decl + R"( + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + + %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %combined = OpLoad %11 %100 + + ; Uses of the combined image sampler are preserved. + ; CHECK: OpCopyObject %11 %combined + + %7 = OpCopyObject %11 %combined + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, + Combined_DeletesCopyObjectOfPtr) { + // OpCopyObject is deleted, and its uses updated. + const std::string kTest = Preamble() + + R"( + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + + ; CHECK: OpName + ; CHECK-NOT: OpDecorate %100 + ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[image_var]] Binding 0 + ; CHECK: OpDecorate %[[sampler_var]] Binding 0 + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK-NOT: %100 = OpVariable + ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[sampler_ptr_ty]] UniformConstant + ; CHECK-DAG: %[[image_var]] = OpVariable %[[image_ptr_ty]] UniformConstant + ; CHECK: = OpFunction + + +)" + BasicTypes() + + " %10 = " + GetParam().image_type_decl + R"( + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + + %100 = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %101 = OpCopyObject %_ptr_UniformConstant_11 %100 + %102 = OpLoad %11 %101 + %103 = OpCopyObject %_ptr_UniformConstant_11 %101 + %104 = OpCopyObject %11 %102 ;; this copy survives + OpReturn + OpFunctionEnd + + ; The OpCopyObject instructions are removed. + ; The load of the combined image+sampler is replaced by a two loads, then + ; a combination operation. The only OpCopyObject that remains is the copy + ; of the copy of the sampled image value. + ; CHECK: %[[im:\d+]] = OpLoad %10 %[[image_var]] + ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[sampler_var]] + ; CHECK: %[[si:\d+]] = OpSampledImage %11 %[[im]] %[[s]] + ; CHECK-NEXT: OpCopyObject %11 %[[si]] + ; CHECK-NEXT: OpReturn +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, ArrayCombined_RemapLoad) { + const std::string kTest = Preamble() + + R"( + OpName %combined "combined" + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + + ; CHECK: OpName + ; CHECK-NOT: OpDecorate %100 + ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[image_var]] Binding 0 + ; CHECK: OpDecorate %[[sampler_var]] Binding 0 + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK: %[[array_image_ty:\w+]] = OpTypeArray %10 %uint_3 + ; CHECK: %[[ptr_array_image_ty:\w+]] = OpTypePointer UniformConstant %[[array_image_ty]] + + ; CHECK: %[[array_sampler_ty:\w+]] = OpTypeArray %[[sampler_ty]] %uint_3 + ; CHECK: %[[ptr_array_sampler_ty:\w+]] = OpTypePointer UniformConstant %[[array_sampler_ty]] + + ; CHECK-NOT: %100 = OpVariable + ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[ptr_array_sampler_ty]] UniformConstant + ; CHECK-DAG: %[[image_var]] = OpVariable %[[ptr_array_image_ty]] UniformConstant + ; CHECK: = OpFunction + + ; The access chain and load is replaced by two access chains, two loads, then + ; a combine operation. + ; CHECK: %[[ptr_im:\d+]] = OpAccessChain %[[image_ptr_ty]] %[[image_var]] %uint_1 + ; CHECK: %[[ptr_s:\d+]] = OpAccessChain %[[sampler_ptr_ty]] %[[sampler_var]] %uint_1 + ; CHECK: %[[im:\d+]] = OpLoad %10 %[[ptr_im]] + ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[ptr_s]] + ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] + +)" + BasicTypes() + + " %10 = " + GetParam().image_type_decl + R"( + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %12 = OpTypeArray %11 %uint_3 +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + + %100 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %ptr = OpAccessChain %_ptr_UniformConstant_11 %100 %uint_1 + %combined = OpLoad %11 %ptr + + ; Uses of the combined image sampler are preserved. + ; CHECK: OpCopyObject %11 %combined + + %7 = OpCopyObject %11 %combined + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassTypeCaseTest, RtArrayCombined_RemapLoad) { + const std::string kTest = Preamble() + + R"( + OpName %combined "combined" + OpDecorate %100 DescriptorSet 0 + OpDecorate %100 Binding 0 + + ; CHECK: OpName + ; CHECK-NOT: OpDecorate %100 + ; CHECK: OpDecorate %[[image_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[sampler_var:\d+]] DescriptorSet 0 + ; CHECK: OpDecorate %[[image_var]] Binding 0 + ; CHECK: OpDecorate %[[sampler_var]] Binding 0 + + ; CHECK: %10 = OpTypeImage % + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + + ; The combined image variable is replaced by an image variable and a sampler variable. + + ; CHECK: %[[array_image_ty:\w+]] = OpTypeRuntimeArray %10 + ; CHECK: %[[ptr_array_image_ty:\w+]] = OpTypePointer UniformConstant %[[array_image_ty]] + + ; CHECK: %[[array_sampler_ty:\w+]] = OpTypeRuntimeArray %[[sampler_ty]] + ; CHECK: %[[ptr_array_sampler_ty:\w+]] = OpTypePointer UniformConstant %[[array_sampler_ty]] + + ; CHECK-NOT: %100 = OpVariable + ; CHECK-DAG: %[[sampler_var]] = OpVariable %[[ptr_array_sampler_ty]] UniformConstant + ; CHECK-DAG: %[[image_var]] = OpVariable %[[ptr_array_image_ty]] UniformConstant + ; CHECK: = OpFunction + + ; The access chain and load is replaced by two access chains, two loads, then + ; a combine operation. + ; CHECK: %[[ptr_im:\d+]] = OpAccessChain %[[image_ptr_ty]] %[[image_var]] %uint_1 + ; CHECK: %[[ptr_s:\d+]] = OpAccessChain %[[sampler_ptr_ty]] %[[sampler_var]] %uint_1 + ; CHECK: %[[im:\d+]] = OpLoad %10 %[[ptr_im]] + ; CHECK: %[[s:\d+]] = OpLoad %[[sampler_ty]] %[[ptr_s]] + ; CHECK: %combined = OpSampledImage %11 %[[im]] %[[s]] + +)" + BasicTypes() + + " %10 = " + GetParam().image_type_decl + R"( + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %12 = OpTypeRuntimeArray %11 +%_ptr_UniformConstant_12 = OpTypePointer UniformConstant %12 + + %100 = OpVariable %_ptr_UniformConstant_12 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + %ptr = OpAccessChain %_ptr_UniformConstant_11 %100 %uint_1 + %combined = OpLoad %11 %ptr + + ; Uses of the combined image sampler are preserved. + ; CHECK: OpCopyObject %11 %combined + + %7 = OpCopyObject %11 %combined + OpReturn + OpFunctionEnd +)"; + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +INSTANTIATE_TEST_SUITE_P(AllCombinedTypes, + SplitCombinedImageSamplerPassTypeCaseTest, + ::testing::ValuesIn(ImageTypeCases())); + +// Remap entry point + +struct EntryPointRemapCase { + const spv_target_env environment = SPV_ENV_VULKAN_1_0; + const char* initial_interface = ""; + const char* expected_interface = nullptr; +}; + +std::ostream& operator<<(std::ostream& os, const EntryPointRemapCase& eprc) { + os << "(env " << spvLogStringForEnv(eprc.environment) << ", init " + << eprc.initial_interface << " -> expect " << eprc.expected_interface + << ")"; + return os; +} + +struct SplitCombinedImageSamplerPassEntryPointRemapTest + : public PassTest<::testing::TestWithParam> { + virtual void SetUp() override { + SetTargetEnv(GetParam().environment); + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } +}; + +std::vector EntryPointInterfaceCases() { + return std::vector{ + {SPV_ENV_VULKAN_1_0, " %in_var %out_var", " %in_var %out_var"}, + {SPV_ENV_VULKAN_1_4, " %combined_var", + " %combined_var_image %combined_var_sampler"}, + {SPV_ENV_VULKAN_1_4, " %combined_var %in_var %out_var", + " %combined_var_image %in_var %out_var %combined_var_sampler"}, + {SPV_ENV_VULKAN_1_4, " %in_var %combined_var %out_var", + " %in_var %combined_var_image %out_var %combined_var_sampler"}, + {SPV_ENV_VULKAN_1_4, " %in_var %out_var %combined_var", + " %in_var %out_var %combined_var_image %combined_var_sampler"}, + }; +} + +TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, + EntryPoint_Combined_UsedInShader) { + const std::string kTest = PreambleFragment(GetParam().initial_interface) + + R"( + OpName %combined "combined" + OpName %combined_var "combined_var" + OpName %in_var "in_var" + OpName %out_var "out_var" + OpDecorate %combined_var DescriptorSet 0 + OpDecorate %combined_var Binding 0 + OpDecorate %in_var BuiltIn FragCoord + OpDecorate %out_var Location 0 + +; CHECK: OpEntryPoint Fragment %main "main")" + + GetParam().expected_interface + R"( +; These clauses ensure the expected interface is the whole interface. +; CHECK-NOT: %{{\d+}} +; CHECK-NOT: %in_var +; CHECK-NOT: %out_var +; CHECK-NOT: %combined_var +; CHECK: OpExecutionMode %main OriginUpperLeft + + ; Check the var names, tracing up through the types. + ; CHECK: %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + ; CHECK: %[[image_ptr_ty:\w+]] = OpTypePointer UniformConstant %10 + ; CHECK: %[[sampler_ty:\d+]] = OpTypeSampler + ; CHECK: %[[sampler_ptr_ty:\w+]] = OpTypePointer UniformConstant %[[sampler_ty]] + ; The combined image variable is replaced by an image variable and a sampler variable. + ; CHECK-DAG: %combined_var_sampler = OpVariable %[[sampler_ptr_ty]] UniformConstant + ; CHECK-DAG: %combined_var_image = OpVariable %[[image_ptr_ty]] UniformConstant + ; CHECK: = OpFunction + + %bool = OpTypeBool +)" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + %11 = OpTypeSampledImage %10 +%_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %in_ptr_v4f = OpTypePointer Input %v4float + %in_var = OpVariable %in_ptr_v4f Input + %out_ptr_v4f = OpTypePointer Output %v4float + %out_var = OpVariable %out_ptr_v4f Output + +%combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + ;CHECK: %main_0 = OpLabel + ;CHECK: OpLoad + + %main_0 = OpLabel + %combined = OpLoad %11 %combined_var + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, + EntryPoint_Combined_UsedOnlyInEntryPointInstruction) { + // If the combined var is in the interface, that is enough to trigger + // its replacement. Otherwise the entry point interface is untouched + // when the combined var is not otherwise used. + const bool combined_var_in_interface = + std::string(GetParam().initial_interface).find("%combined_var") != + std::string::npos; + if (combined_var_in_interface) { + const std::string kTest = PreambleFragment(GetParam().initial_interface) + + R"( + OpName %combined_var "combined_var" + OpName %in_var "in_var" + OpName %out_var "out_var" + OpDecorate %combined_var DescriptorSet 0 + OpDecorate %combined_var Binding 0 + OpDecorate %in_var BuiltIn FragCoord + OpDecorate %out_var Location 0 + + ; CHECK: OpEntryPoint Fragment %main "main")" + + GetParam().expected_interface + R"( + ; These clauses ensure the expected interface is the whole interface. + ; CHECK-NOT: %{{\d+}} + ; CHECK-NOT: %in_var + ; CHECK-NOT: %out_var + ; CHECK-NOT: %combined_var + ; CHECK: OpExecutionMode %main OriginUpperLeft + + %bool = OpTypeBool + )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + %11 = OpTypeSampledImage %10 + %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %in_ptr_v4f = OpTypePointer Input %v4float + %in_var = OpVariable %in_ptr_v4f Input + %out_ptr_v4f = OpTypePointer Output %v4float + %out_var = OpVariable %out_ptr_v4f Output + + ; %combined_var is not used! + %combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd + )"; + auto [disasm, status] = + SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; + } +} + +TEST_P(SplitCombinedImageSamplerPassEntryPointRemapTest, + EntryPoint_Combined_Unused) { + // If the combined var is in the interface, that is enough to trigger + // its replacement. Otherwise the entry point interface is untouched + // when the combined var is not otherwise used. + const bool combined_var_in_interface = + std::string(GetParam().initial_interface).find("%combined_var") != + std::string::npos; + if (!combined_var_in_interface) { + const std::string kTest = PreambleFragment(GetParam().initial_interface) + + R"( + ; CHECK: OpEntryPoint Fragment %main "main")" + + GetParam().initial_interface // Note this is the + // intial interface + + R"( + ; These clauses ensure the expected interface is the whole interface. + ; CHECK-NOT: %{{\d+}} + ; CHECK-NOT: %in_var + ; CHECK-NOT: %out_var + ; CHECK-NOT: %combined_var + ; CHECK: OpExecutionMode %main OriginUpperLeft + + ; The variable disappears. + ; CHECK-NOT: %combined_var = + ; CHECK: OpFunctionEnd + OpName %combined_var "combined_var" + OpName %in_var "in_var" + OpName %out_var "out_var" + OpDecorate %combined_var DescriptorSet 0 + OpDecorate %combined_var Binding 0 + OpDecorate %in_var BuiltIn FragCoord + OpDecorate %out_var Location 0 + + + %bool = OpTypeBool + )" + BasicTypes() + R"( %10 = OpTypeImage %float 2D 0 0 0 1 Unknown + %11 = OpTypeSampledImage %10 + %_ptr_UniformConstant_11 = OpTypePointer UniformConstant %11 + %in_ptr_v4f = OpTypePointer Input %v4float + %in_var = OpVariable %in_ptr_v4f Input + %out_ptr_v4f = OpTypePointer Output %v4float + %out_var = OpVariable %out_ptr_v4f Output + + ; %combined_var is not used! + %combined_var = OpVariable %_ptr_UniformConstant_11 UniformConstant + %main = OpFunction %void None %voidfn + %main_0 = OpLabel + OpReturn + OpFunctionEnd +)"; + auto [disasm, status] = + SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; + } +} + +INSTANTIATE_TEST_SUITE_P(EntryPointRemap, + SplitCombinedImageSamplerPassEntryPointRemapTest, + ::testing::ValuesIn(EntryPointInterfaceCases())); + +// Remap function types + +struct FunctionTypeCase { + const char* initial_type_params = ""; + const char* expected_type_params = ""; +}; + +std::ostream& operator<<(std::ostream& os, const FunctionTypeCase& ftc) { + os << "(init " << ftc.initial_type_params << " -> expect " + << ftc.expected_type_params << ")"; + return os; +} + +struct SplitCombinedImageSamplerPassFunctionTypeTest + : public PassTest<::testing::TestWithParam> { + virtual void SetUp() override { + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | + SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); + } +}; + +std::vector FunctionTypeCases() { + return std::vector{ + {"", ""}, + {" %image_ty", " %image_ty"}, + {" %sampler_ty", " %sampler_ty"}, + {" %sampled_image_ty", " %image_ty %sampler_ty"}, + {" %uint %sampled_image_ty %float", + " %uint %image_ty %sampler_ty %float"}, + {" %ptr_sampled_image_ty", + " %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty"}, + {" %uint %ptr_sampled_image_ty %float", + " %uint %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " + "%float"}, + {" %uint %ptr_sampled_image_ty %ptr_sampled_image_ty %float", + " %uint %_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " + "%_ptr_UniformConstant_image_ty %_ptr_UniformConstant_sampler_ty " + "%float"}, + }; +} + +TEST_P(SplitCombinedImageSamplerPassFunctionTypeTest, + ReplaceCombinedImageSamplersOnly) { + const std::string kTest = Preamble() + +R"( + OpName %f_ty "f_ty" + OpName %sampler_ty "sampler_ty" + OpName %image_ty "image_ty" + OpName %sampled_image_ty "sampled_image_ty" + OpName %ptr_sampled_image_ty "sampled_image_ty" + + )" + BasicTypes() + R"( + + %sampler_ty = OpTypeSampler + %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %sampled_image_ty = OpTypeSampledImage %image_ty + %ptr_sampled_image_ty = OpTypePointer UniformConstant %sampled_image_ty + + %f_ty = OpTypeFunction %float)" + + GetParam().initial_type_params + R"( + + ; CHECK: %f_ty = OpTypeFunction %float)" + + GetParam().expected_type_params + R"( +)" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_P(SplitCombinedImageSamplerPassFunctionTypeTest, AvoidDuplicateType) { + // SPIR-V does not allow duplicate non-aggregate types. That includes function + // types. Test that when replacing function type parameters would cause a + // collision, that the original function type is replaced with the new one. + const std::string initial_params(GetParam().initial_type_params); + const std::string expected_params(GetParam().expected_type_params); + const std::string kTest = Preamble() + +R"( + OpName %sampler_ty "sampler_ty" + OpName %image_ty "image_ty" + OpName %sampled_image_ty "sampled_image_ty" + OpName %_ptr_UniformConstant_sampler_ty "_ptr_UniformConstant_sampler_ty" + OpName %_ptr_UniformConstant_image_ty "_ptr_UniformConstant_image_ty" + OpName %ptr_sampled_image_ty "sampled_image_ty" + OpName %dest_ty "dest_ty" + + )" + BasicTypes() + R"( + + %sampler_ty = OpTypeSampler + %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %sampled_image_ty = OpTypeSampledImage %image_ty + %ptr_sampled_image_ty = OpTypePointer UniformConstant %sampled_image_ty + %_ptr_UniformConstant_image_ty = OpTypePointer UniformConstant %image_ty + %_ptr_UniformConstant_sampler_ty = OpTypePointer UniformConstant %sampler_ty + + %100 = OpTypeFunction %float)" + + initial_params + R"( + %dest_ty = OpTypeFunction %float)" + + expected_params + R"( + + ; CHECK: OpTypeSampler + ; CHECK-NOT: %100 = + ; CHECK: %dest_ty = OpTypeFunction %float)" + + expected_params + R"( + ; CHECK-NOT: %100 = + ; CHECK: %main = OpFunction +)" + Main(); + // The original source is invalid if initial and expected params are the same, + // because the type is already duplicated. + // Only test when they are different. + if (initial_params != expected_params) { + auto [disasm, status] = + SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; + } +} + +INSTANTIATE_TEST_SUITE_P(FunctionTypeRemap, + SplitCombinedImageSamplerPassFunctionTypeTest, + ::testing::ValuesIn(FunctionTypeCases())); + +// Test array and runtime-array cases for function type replacement. + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceSampledImageArg) { + // The original module has a sampled image type, used only as a function + // parameter. We still want to replace it. But no other sampled-image types + // exist. This proves that the pass needs a sampled_image_used_as_param_ + // state variable. + const std::string kTest = Preamble() + +R"( + OpName %f_ty "f_ty" + OpName %sampler_ty "sampler_ty" + OpName %image_ty "image_ty" + OpName %sampled_image_ty "sampled_image_ty" + + )" + BasicTypes() + R"( + + %sampler_ty = OpTypeSampler + %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %sampled_image_ty = OpTypeSampledImage %image_ty + + %f_ty = OpTypeFunction %float %sampled_image_ty %float + ; CHECK: %f_ty = OpTypeFunction %float %image_ty %sampler_ty %float +)" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceArrayArg) { + const std::string kTest = Preamble() + +R"( + OpName %f_ty "f_ty" + OpName %sampler_ty "sampler_ty" + OpName %image_ty "image_ty" + OpName %sampled_image_ty "sampled_image_ty" + OpName %ptr_array_si_ty "ptr_array_si_ty" + + )" + BasicTypes() + R"( + + %sampler_ty = OpTypeSampler + %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %sampled_image_ty = OpTypeSampledImage %image_ty + %array_si_ty = OpTypeArray %sampled_image_ty %uint_3 + %ptr_array_si_ty = OpTypePointer UniformConstant %array_si_ty + + ; CHECK: %[[array_i_ty:\w+]] = OpTypeArray %image_ty %uint_3 + ; CHECK: %[[ptr_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] + ; CHECK: %[[array_s_ty:\w+]] = OpTypeArray %sampler_ty %uint_3 + ; CHECK: %[[ptr_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] + + %f_ty = OpTypeFunction %float %uint %ptr_array_si_ty %float + ; CHECK: %f_ty = OpTypeFunction %float %uint %[[ptr_array_i_ty]] %[[ptr_array_s_ty]] %float +)" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionType_ReplaceRtArrayArg) { + const std::string kTest = Preamble() + +R"( + OpName %f_ty "f_ty" + OpName %sampler_ty "sampler_ty" + OpName %image_ty "image_ty" + OpName %sampled_image_ty "sampled_image_ty" + OpName %ptr_array_si_ty "ptr_array_si_ty" + + )" + BasicTypes() + R"( + + %sampler_ty = OpTypeSampler + %image_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %sampled_image_ty = OpTypeSampledImage %image_ty + %array_si_ty = OpTypeRuntimeArray %sampled_image_ty + %ptr_array_si_ty = OpTypePointer UniformConstant %array_si_ty + + ; CHECK: %[[array_i_ty:\w+]] = OpTypeRuntimeArray %image_ty + ; CHECK: %[[ptr_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] + ; CHECK: %[[array_s_ty:\w+]] = OpTypeRuntimeArray %sampler_ty + ; CHECK: %[[ptr_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] + + %f_ty = OpTypeFunction %float %uint %ptr_array_si_ty %float + ; CHECK: %f_ty = OpTypeFunction %float %uint %[[ptr_array_i_ty]] %[[ptr_array_s_ty]] %float +)" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +// Remap function bodies + +std::string NamedITypes() { + return R"( + OpName %f "f" + OpName %f_ty "f_ty" + OpName %i_ty "i_ty" + OpName %s_ty "s_ty" + OpName %p_i_ty "p_i_ty" + OpName %p_s_ty "p_s_ty" +)"; +} + +std::string NamedCombinedTypes() { + return R"( + OpName %si_ty "si_ty" + OpName %p_si_ty "p_si_ty" + OpName %array_si_ty "array_si_ty" + OpName %rtarray_si_ty "rtarray_si_ty" + OpName %p_array_si_ty "p_array_si_ty" + OpName %p_rtarray_si_ty "p_rtarray_si_ty" +)"; +} + +std::string NamedCaller() { + return R"( + OpName %caller_ty "caller_ty" + OpName %caller "caller" + OpName %caller_entry "caller_entry" + OpName %caller_call "caller_call" + OpName %caller_arg "caller_arg" +)"; +} + +std::string ITypes() { + return R"( + %i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown + %s_ty = OpTypeSampler + %p_i_ty = OpTypePointer UniformConstant %i_ty + %p_s_ty = OpTypePointer UniformConstant %s_ty +)"; +} + +std::string CombinedTypes() { + return R"( + %si_ty = OpTypeSampledImage %i_ty + %p_si_ty = OpTypePointer UniformConstant %si_ty + %array_si_ty = OpTypeArray %si_ty %uint_3 + %p_array_si_ty = OpTypePointer UniformConstant %array_si_ty + %rtarray_si_ty = OpTypeRuntimeArray %si_ty + %p_rtarray_si_ty = OpTypePointer UniformConstant %rtarray_si_ty +)"; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_ScalarNoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + BasicTypes() + ITypes() + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %float %i_ty %s_ty %p_i_ty %p_s_ty + %f_ty = OpTypeFunction %float %i_ty %s_ty %p_i_ty %p_s_ty + + ; CHECK: %f = OpFunction %float None %f_ty + ; CHECK-NEXT: OpFunctionParameter %i_ty + ; CHECK-NEXT: OpFunctionParameter %s_ty + ; CHECK-NEXT: OpFunctionParameter %p_i_ty + ; CHECK-NEXT: OpFunctionParameter %p_s_ty + ; CHECK-NEXT: OpLabel + %f = OpFunction %float None %f_ty + %100 = OpFunctionParameter %i_ty + %101 = OpFunctionParameter %s_ty + %102 = OpFunctionParameter %p_i_ty + %103 = OpFunctionParameter %p_s_ty + %110 = OpLabel + OpReturnValue %float_0 + OpFunctionEnd + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionBody_SampledImage_OpImageSample) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + BasicTypes() + ITypes() + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %v4float %uint %i_ty %s_ty %float + %f_ty = OpTypeFunction %v4float %uint %si_ty %float + + ; CHECK: %f = OpFunction %v4float None %f_ty + ; CHECK: OpFunctionParameter %uint + ; CHECK-NEXT: %[[i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: OpFunctionParameter %float + ; CHECK-NEXT: OpLabel + ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] + ; CHECK-NEXT: %200 = OpImageSampleExplicitLod %v4float %[[si]] %13 Lod %float_0 + ; CHECK-NEXT: OpReturnValue %200 + + %f = OpFunction %v4float None %f_ty + %100 = OpFunctionParameter %uint + %101 = OpFunctionParameter %si_ty ; replace this + %110 = OpFunctionParameter %float + %120 = OpLabel + %200 = OpImageSampleExplicitLod %v4float %101 %13 Lod %float_0 + OpReturnValue %200 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_SampledImage_OpImage) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + BasicTypes() + ITypes() + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %uint %i_ty %s_ty %float + %f_ty = OpTypeFunction %void %uint %si_ty %float + + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK: OpFunctionParameter %uint + ; CHECK-NEXT: %[[i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: OpFunctionParameter %float + ; CHECK-NEXT: OpLabel + ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] + ; CHECK-NEXT: %200 = OpImage %i_ty %[[si]] + ; CHECK-NEXT: OpReturn + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %uint + %101 = OpFunctionParameter %si_ty ; replace this + %110 = OpFunctionParameter %float + %120 = OpLabel + %200 = OpImage %i_ty %101 + OpReturn + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionBody_PtrSampledImage) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + BasicTypes() + ITypes() + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %v4float %uint %p_i_ty %p_s_ty %float + %f_ty = OpTypeFunction %v4float %uint %p_si_ty %float + + ; CHECK: %f = OpFunction %v4float None %f_ty + ; CHECK-NEXT: OpFunctionParameter %uint + ; CHECK-NEXT: %[[pi:\w+]] = OpFunctionParameter %p_i_ty + ; CHECK-NEXT: %[[ps:\w+]] = OpFunctionParameter %p_s_ty + ; CHECK-NEXT: OpFunctionParameter %float + ; CHECK-NEXT: OpLabel + ; CHECK-NEXT: %[[i:\w+]] = OpLoad %i_ty %[[pi]] + ; CHECK-NEXT: %[[s:\w+]] = OpLoad %s_ty %[[ps]] + ; CHECK-NEXT: %[[si:\w+]] = OpSampledImage %si_ty %[[i]] %[[s]] + ; CHECK-NEXT: %200 = OpImageSampleExplicitLod %v4float %[[si]] %13 Lod %float_0 + ; CHECK-NEXT: OpReturnValue %200 + + %f = OpFunction %v4float None %f_ty + %100 = OpFunctionParameter %uint + %101 = OpFunctionParameter %p_si_ty ; replace this + %110 = OpFunctionParameter %float + %120 = OpLabel + %121 = OpLoad %si_ty %101 + %200 = OpImageSampleExplicitLod %v4float %121 %13 Lod %float_0 + OpReturnValue %200 + OpFunctionEnd + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_NoImageOrSampler_NoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + + BasicTypes() + ITypes() + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %uint %float + %f_ty = OpTypeFunction %void %uint %float + %caller_ty = OpTypeFunction %float ; make it return non-void otherwise it's just like main + + ; The called function does not change + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: = OpFunctionParameter %uint + ; CHECK-NEXT: = OpFunctionParameter %float + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %uint + %101 = OpFunctionParameter %float + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; The caller does not change + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_arg = OpCopyObject %uint %uint_0 + ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg %float_0 + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty +%caller_entry = OpLabel + %caller_arg = OpCopyObject %uint %uint_0 + %caller_call = OpFunctionCall %void %f %caller_arg %float_0 + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + // We still get a success-with-change result because the boilerplate included + // combined types, which were removed. + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_Image_NoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + + BasicTypes() + ITypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %i_ty + %f_ty = OpTypeFunction %void %i_ty + %caller_ty = OpTypeFunction %float %i_ty + + ; The called function does not change + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: = OpFunctionParameter %i_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %i_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; The caller does not change + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %caller_arg = OpFunctionParameter %i_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %i_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_Sampler_NoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + + BasicTypes() + ITypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %s_ty + %f_ty = OpTypeFunction %void %s_ty + %caller_ty = OpTypeFunction %float %s_ty + + ; The called function does not change + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: = OpFunctionParameter %s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %s_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; The caller does not change + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %caller_arg = OpFunctionParameter %s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %s_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrImage_NoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + + BasicTypes() + ITypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %p_i_ty + %f_ty = OpTypeFunction %void %p_i_ty + %caller_ty = OpTypeFunction %float %p_i_ty + + ; The called function does not change + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: = OpFunctionParameter %p_i_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %p_i_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; The caller does not change + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %caller_arg = OpFunctionParameter %p_i_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %p_i_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrSampler_NoChange) { + const std::string kTest = Preamble() + NamedITypes() + NamedCaller() + + BasicTypes() + ITypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %p_s_ty + %f_ty = OpTypeFunction %void %p_s_ty + %caller_ty = OpTypeFunction %float %p_s_ty + + ; The called function does not change + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: = OpFunctionParameter %p_s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %p_s_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; The caller does not change + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %caller_arg = OpFunctionParameter %p_s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: OpFunctionCall %void %f %caller_arg + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %p_s_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_SampledImage_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty + %f_ty = OpTypeFunction %void %si_ty + %caller_ty = OpTypeFunction %float %si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_SampledImageDuplicatedArg_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty + %f_ty = OpTypeFunction %void %si_ty %si_ty + %caller_ty = OpTypeFunction %float %si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %si_ty + %101 = OpFunctionParameter %si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_SampledImageTwoDistinct_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty + %f_ty = OpTypeFunction %void %si_ty %si_ty + %caller_ty = OpTypeFunction %float %si_ty %si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %si_ty + %101 = OpFunctionParameter %si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i_0:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s_0:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[caller_i_1:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s_1:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i_0]] %[[caller_s_0]] %[[caller_i_1]] %[[caller_s_1]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %si_ty + %201 = OpFunctionParameter %si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg %201 + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_SampledImageAndCopy_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %i_ty %s_ty %i_ty %s_ty + %f_ty = OpTypeFunction %void %si_ty %si_ty + ; CHECK: %caller_ty = OpTypeFunction %float %i_ty %s_ty + %caller_ty = OpTypeFunction %float %si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i_0:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_0:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[callee_i_1:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s_1:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %si_ty + %101 = OpFunctionParameter %si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %si_ty +%caller_entry = OpLabel + %copy = OpCopyObject %si_ty %caller_arg + %caller_call = OpFunctionCall %void %f %caller_arg %copy + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_SampledImageSurrounded_Split) { + // Test indexing by surrounding the sampled image parameter with other + // arguments that should not be touched. + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %float %i_ty %s_ty %uint + %f_ty = OpTypeFunction %void %float %si_ty %uint + ; CHECK: %caller_ty = OpTypeFunction %float %uint %i_ty %s_ty %float + %caller_ty = OpTypeFunction %float %uint %si_ty %float + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_f:\w+]] = OpFunctionParameter %float + ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[callee_u:\w+]] = OpFunctionParameter %uint + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %99 = OpFunctionParameter %float + %100 = OpFunctionParameter %si_ty + %101 = OpFunctionParameter %uint + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[u_param:\w+]] = OpFunctionParameter %uint + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %i_ty + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %s_ty + ; CHECK-NEXT: %[[f_param:\w+]] = OpFunctionParameter %float + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[f_param]] %[[caller_i]] %[[caller_s]] %[[u_param]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %200 = OpFunctionParameter %uint + %caller_arg = OpFunctionParameter %si_ty + %201 = OpFunctionParameter %float +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %201 %caller_arg %200 + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, FunctionCall_PtrSampledImage_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %f_ty = OpTypeFunction %void %p_i_ty %p_s_ty + %f_ty = OpTypeFunction %void %p_si_ty + %caller_ty = OpTypeFunction %float %p_si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %p_i_ty + ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %p_s_ty + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %p_si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %p_i_ty + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %p_s_ty + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %p_si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_PtrArraySampledImage_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %[[array_i_ty:\w+]] = OpTypeArray %i_ty %uint_3 + ; CHECK: %[[p_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] + ; CHECK: %[[array_s_ty:\w+]] = OpTypeArray %s_ty %uint_3 + ; CHECK: %[[p_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] + + ; CHECK: %f_ty = OpTypeFunction %void %[[p_array_i_ty]] %[[p_array_s_ty]] + %f_ty = OpTypeFunction %void %p_array_si_ty + %caller_ty = OpTypeFunction %float %p_array_si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] + ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %p_array_si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %p_array_si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +TEST_F(SplitCombinedImageSamplerPassTest, + FunctionCall_PtrRtArraySampledImage_Split) { + const std::string kTest = Preamble() + NamedITypes() + NamedCombinedTypes() + + NamedCaller() + BasicTypes() + ITypes() + + CombinedTypes() + R"( + + ; CHECK: %[[array_i_ty:\w+]] = OpTypeRuntimeArray %i_ty + ; CHECK: %[[p_array_i_ty:\w+]] = OpTypePointer UniformConstant %[[array_i_ty]] + ; CHECK: %[[array_s_ty:\w+]] = OpTypeRuntimeArray + ; CHECK: %[[p_array_s_ty:\w+]] = OpTypePointer UniformConstant %[[array_s_ty]] + + ; CHECK: %f_ty = OpTypeFunction %void %[[p_array_i_ty]] %[[p_array_s_ty]] + %f_ty = OpTypeFunction %void %p_rtarray_si_ty + %caller_ty = OpTypeFunction %float %p_rtarray_si_ty + + ; Call function arg is split. We've checked these details in other tests. + ; CHECK: %f = OpFunction %void None %f_ty + ; CHECK-NEXT: %[[callee_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] + ; CHECK-NEXT: %[[callee_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] + ; CHECK-NEXT: = OpLabel + ; CHECK-NEXT: OpReturn + ; CHECK-NEXT: OpFunctionEnd + + %f = OpFunction %void None %f_ty + %100 = OpFunctionParameter %p_rtarray_si_ty + %110 = OpLabel + OpReturn + OpFunctionEnd + + ; CHECK: %caller = OpFunction %float None %caller_ty + ; CHECK-NEXT: %[[caller_i:\w+]] = OpFunctionParameter %[[p_array_i_ty]] + ; CHECK-NEXT: %[[caller_s:\w+]] = OpFunctionParameter %[[p_array_s_ty]] + ; CHECK-NEXT: %caller_entry = OpLabel + ; CHECK-NEXT: %caller_call = OpFunctionCall %void %f %[[caller_i]] %[[caller_s]] + ; CHECK-NEXT: OpReturnValue %float_0 + ; CHECK-NEXT: OpFunctionEnd + + %caller = OpFunction %float None %caller_ty + %caller_arg = OpFunctionParameter %p_rtarray_si_ty +%caller_entry = OpLabel + %caller_call = OpFunctionCall %void %f %caller_arg + OpReturnValue %float_0 + OpFunctionEnd + + )" + Main(); + + auto [disasm, status] = SinglePassRunAndMatch( + kTest, /* do_validation= */ true); + EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm; +} + +} // namespace +} // namespace opt +} // namespace spvtools diff --git a/third_party/spirv-tools/test/opt/trim_capabilities_pass_test.cpp b/third_party/spirv-tools/test/opt/trim_capabilities_pass_test.cpp index 1569ad73de..fbc10c8673 100644 --- a/third_party/spirv-tools/test/opt/trim_capabilities_pass_test.cpp +++ b/third_party/spirv-tools/test/opt/trim_capabilities_pass_test.cpp @@ -1401,6 +1401,244 @@ TEST_F(TrimCapabilitiesPassTest, EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); } +TEST_F(TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemainsSimplePointer_Vulkan1_0) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability Float16 + OpCapability StorageBuffer16BitAccess + +; CHECK: OpCapability StorageBuffer16BitAccess +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %half = OpTypeFloat 16 + %struct = OpTypeStruct %half + %ptr = OpTypePointer StorageBuffer %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_0); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange); +} + +TEST_F(TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemainsSimplePointer_Vulkan1_1) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability Float16 + OpCapability StorageBuffer16BitAccess + +; CHECK: OpCapability StorageBuffer16BitAccess +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %half = OpTypeFloat 16 + %struct = OpTypeStruct %half + %ptr = OpTypePointer StorageBuffer %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_1); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange); +} + +TEST_F( + TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemainsSimplePointerUshortPhysicalStorage_Vulkan1_1) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability StorageBuffer16BitAccess + OpCapability PhysicalStorageBufferAddresses + +; CHECK: OpCapability StorageBuffer16BitAccess +; CHECK: OpCapability PhysicalStorageBufferAddresses +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" +; CHECK-NOT: OpCapability Int16 + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %ushort = OpTypeInt 16 0 + %struct = OpTypeStruct %ushort + %ptr = OpTypePointer PhysicalStorageBuffer %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_1); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange); +} + +TEST_F( + TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemainsSimplePointerUshortStorageBuffer_Vulkan1_1) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability StorageBuffer16BitAccess + +; CHECK: OpCapability StorageBuffer16BitAccess +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" +; CHECK-NOT: OpCapability Int16 + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %ushort = OpTypeInt 16 0 + %struct = OpTypeStruct %ushort + %ptr = OpTypePointer StorageBuffer %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_1); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange); +} + +TEST_F( + TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemainsSimplePointerUshortRecordBuffer_Vulkan1_1) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability RayTracingKHR + OpCapability StorageBuffer16BitAccess + OpExtension "SPV_KHR_ray_tracing" + +; CHECK-NOT: OpCapability Int16 +; CHECK: OpCapability RayTracingKHR +; CHECK: OpCapability StorageBuffer16BitAccess +; CHECK: OpExtension "SPV_KHR_ray_tracing" +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %ushort = OpTypeInt 16 0 + %struct = OpTypeStruct %ushort + %ptr = OpTypePointer ShaderRecordBufferKHR %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_1); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange); +} + +TEST_F(TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_TrimRecordBuffer_Vulkan1_1) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability RayTracingKHR + OpCapability StorageBuffer16BitAccess + OpExtension "SPV_KHR_ray_tracing" + +; CHECK-NOT: OpCapability RayTracingKHR +; CHECK-NOT: OpCapability StorageBuffer16BitAccess +; CHECK-NOT: OpExtension "SPV_KHR_ray_tracing" + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + OpDecorate %struct Block + %void = OpTypeVoid + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + SetTargetEnv(SPV_ENV_VULKAN_1_1); + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); +} + +TEST_F(TrimCapabilitiesPassTest, + StorageBuffer16BitAccess_RemovedSimplePointer) { + // See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354 + static_assert(spv::Capability::StorageUniformBufferBlock16 == + spv::Capability::StorageBuffer16BitAccess); + + const std::string kTest = R"( + OpCapability Shader + OpCapability Float16 + OpCapability StorageBuffer16BitAccess + OpExtension "SPV_KHR_16bit_storage" + +; CHECK-NOT: OpCapability StorageBuffer16BitAccess +; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage" + + OpMemoryModel Logical GLSL450 + OpEntryPoint GLCompute %2 "main" + %void = OpTypeVoid + %half = OpTypeFloat 16 + %struct = OpTypeStruct %half + %ptr = OpTypePointer Function %struct + %1 = OpTypeFunction %void + %2 = OpFunction %void None %1 + %3 = OpLabel + OpReturn + OpFunctionEnd + )"; + const auto result = + SinglePassRunAndMatch(kTest, /* skip_nop= */ false); + EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange); +} + TEST_F(TrimCapabilitiesPassTest, FragmentShaderInterlock_RemovedIfNotUsed) { const std::string kTest = R"( OpCapability Shader diff --git a/third_party/spirv-tools/test/string_utils_test.cpp b/third_party/spirv-tools/test/string_utils_test.cpp index 58514158f5..b4ce29f5f9 100644 --- a/third_party/spirv-tools/test/string_utils_test.cpp +++ b/third_party/spirv-tools/test/string_utils_test.cpp @@ -12,10 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include - -#include "gtest/gtest.h" #include "source/util/string_utils.h" + +#include +#include + +#include "gmock/gmock.h" +#include "source/util/small_vector.h" #include "spirv-tools/libspirv.h" namespace spvtools { @@ -186,6 +189,50 @@ TEST(CardinalToOrdinal, Test) { EXPECT_EQ("1225th", CardinalToOrdinal(1225)); } +using MakeVectorMakeStringRoundTripTest = ::testing::TestWithParam; + +TEST_P(MakeVectorMakeStringRoundTripTest, ViaStdVector) { + const std::string str = GetParam(); + const auto& vec = MakeVector>(str); + // Allow for the terminating null byte that must be present. + const auto expected_vec_len = (str.size() + 4) / 4; + EXPECT_EQ(vec.size(), expected_vec_len); + const std::string back_to_string = MakeString(vec); + EXPECT_EQ(str, back_to_string); +} + +TEST_P(MakeVectorMakeStringRoundTripTest, ViaSmallVector1) { + const std::string str = GetParam(); + // Allow for the terminating null byte that must be present. + const auto expected_vec_len = (str.size() + 4) / 4; + const auto& vec = MakeVector>(str); + EXPECT_EQ(vec.size(), expected_vec_len); + const std::string back_to_string = MakeString(vec); + EXPECT_EQ(str, back_to_string); +} + +TEST_P(MakeVectorMakeStringRoundTripTest, ViaSmallVector100) { + const std::string str = GetParam(); + // Allow for the terminating null byte that must be present. + const auto expected_vec_len = (str.size() + 4) / 4; + const auto& vec = + MakeVector>(str); + EXPECT_EQ(vec.size(), expected_vec_len); + const std::string back_to_string = MakeString(vec); + EXPECT_EQ(str, back_to_string); +} + +INSTANTIATE_TEST_SUITE_P(Examples, MakeVectorMakeStringRoundTripTest, + testing::ValuesIn(std::vector{ + "", + "a", + "bc", + "def", + "ghij", + "klmno", + "dustclouds disappear without a trace", + })); + } // namespace } // namespace utils } // namespace spvtools diff --git a/third_party/spirv-tools/test/tools/opt/flags.py b/third_party/spirv-tools/test/tools/opt/flags.py index 52a43c51af..5f56e1d0b8 100644 --- a/third_party/spirv-tools/test/tools/opt/flags.py +++ b/third_party/spirv-tools/test/tools/opt/flags.py @@ -113,7 +113,7 @@ class TestValidPassFlags(expect.ValidObjectFile1_6, 'remove-duplicates', 'replace-invalid-opcode', 'ssa-rewrite', - 'scalar-replacement=100', + 'scalar-replacement=0', 'scalar-replacement=42', 'strength-reduction', 'strip-debug', @@ -148,7 +148,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_6, 'eliminate-local-single-block', 'eliminate-local-single-store', 'eliminate-dead-code-aggressive', - 'scalar-replacement=100', + 'scalar-replacement=0', 'convert-local-access-chains', 'eliminate-local-single-block', 'eliminate-local-single-store', @@ -162,7 +162,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_6, 'redundancy-elimination', 'combine-access-chains', 'simplify-instructions', - 'scalar-replacement=100', + 'scalar-replacement=0', 'convert-local-access-chains', 'eliminate-local-single-block', 'eliminate-local-single-store', diff --git a/third_party/spirv-tools/test/val/val_bitwise_test.cpp b/third_party/spirv-tools/test/val/val_bitwise_test.cpp index b849e7b778..6033d9b4b7 100644 --- a/third_party/spirv-tools/test/val/val_bitwise_test.cpp +++ b/third_party/spirv-tools/test/val/val_bitwise_test.cpp @@ -427,6 +427,16 @@ TEST_F(ValidateBitwise, OpBitFieldInsertNot32Vulkan) { HasSubstr("Expected 32-bit int type for Base operand: BitFieldInsert")); } +TEST_F(ValidateBitwise, OpBitFieldInsertNot32Allow) { + const std::string body = R"( + %val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %s32_1 %s32_2 + )"; + + CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0); + spvValidatorOptionsSetAllowVulkan32BitBitwise(getValidatorOptions(), true); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + TEST_F(ValidateBitwise, OpBitFieldSExtractSuccess) { const std::string body = R"( %val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2 @@ -607,10 +617,8 @@ TEST_F(ValidateBitwise, OpBitCountBaseNotInt) { %val1 = OpBitCount %u32 %f64_1 )"; - CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); - EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-Base-04781")); + CompileSuccessfully(GenerateShaderCode(body).c_str()); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); EXPECT_THAT( getDiagnosticString(), HasSubstr( diff --git a/third_party/spirv-tools/test/val/val_builtins_test.cpp b/third_party/spirv-tools/test/val/val_builtins_test.cpp index ee9b68d70d..d2a77e2790 100644 --- a/third_party/spirv-tools/test/val/val_builtins_test.cpp +++ b/third_party/spirv-tools/test/val/val_builtins_test.cpp @@ -1422,16 +1422,6 @@ INSTANTIATE_TEST_SUITE_P( Values(TestResult(SPV_ERROR_INVALID_DATA, "to be used only with Fragment execution model")))); -INSTANTIATE_TEST_SUITE_P( - SampleMaskWrongStorageClass, - ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, - Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"), - Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"), - Values(TestResult( - SPV_ERROR_INVALID_DATA, - "Vulkan spec allows BuiltIn SampleMask to be only used for " - "variables with Input or Output storage class")))); - INSTANTIATE_TEST_SUITE_P( SampleMaskNotArray, ValidateVulkanCombineBuiltInExecutionModelDataTypeResult, diff --git a/third_party/spirv-tools/test/val/val_decoration_test.cpp b/third_party/spirv-tools/test/val/val_decoration_test.cpp index a15558b930..bc9abf22ad 100644 --- a/third_party/spirv-tools/test/val/val_decoration_test.cpp +++ b/third_party/spirv-tools/test/val/val_decoration_test.cpp @@ -7076,7 +7076,7 @@ OpFunctionEnd CompileSuccessfully(spirv, env); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env)); EXPECT_THAT(getDiagnosticString(), - AnyVUID("VUID-StandaloneSpirv-Component-04924")); + AnyVUID("VUID-StandaloneSpirv-Component-10583")); EXPECT_THAT(getDiagnosticString(), HasSubstr("Component decoration specified for type")); EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a scalar or vector")); @@ -10394,7 +10394,7 @@ TEST_F(ValidateDecorations, ComponentMultipleArrays) { OpDecorate %gl_PerVertex Block OpDecorate %FOO Component 2 OpDecorate %FOO Location 1 - OpDecorate %FOO0 Location 1 + OpDecorate %FOO0 Location 4 OpDecorate %FOO0 Component 0 %void = OpTypeVoid %3 = OpTypeFunction %void @@ -10697,6 +10697,307 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); } +TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPre1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%block = OpTypeStruct %int +%ptr_function_block = OpTypePointer Function %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_function_block Function +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2)); +} + +TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPost1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block Block +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%block = OpTypeStruct %int +%ptr_function_block = OpTypePointer Function %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_function_block Function +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePre1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%block = OpTypeStruct %int +%ptr_private_block = OpTypePointer Private %block +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_private_block Private +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); +} + +TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePost1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%block = OpTypeStruct %int +%ptr_private_block = OpTypePointer Private %block +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_private_block Private +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroupExplicitLayout) { + const std::string spirv = R"( +OpCapability Shader +OpCapability WorkgroupMemoryExplicitLayoutKHR +OpExtension "SPV_KHR_workgroup_memory_explicit_layout" +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%array = OpTypeArray %int %int_4 +%ptr_wg_block = OpTypePointer Workgroup %array +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_wg_block Workgroup +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); +} + +TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroup) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %array ArrayStride 4 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%array = OpTypeArray %int %int_4 +%ptr_wg_block = OpTypePointer Workgroup %array +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_wg_block Workgroup +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutArrayStrideUniformConstant) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %array ArrayStride 4 +OpDecorate %var DescriptorSet 0 +OpDecorate %var Binding 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%sampler = OpTypeSampler +%array = OpTypeArray %sampler %int_4 +%ptr_uc_block = OpTypePointer UniformConstant %array +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_uc_block UniformConstant +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutMatrixStrideFunctionPost1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpMemberDecorate %block 0 MatrixStride 16 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%mat4x4 = OpTypeMatrix %v4float 4 +%block = OpTypeStruct %mat4x4 +%ptr_function_block = OpTypePointer Function %block +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_function_block Function +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutNestedMatrixStrideFunctionPost1p4) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpMemberDecorate %block 0 MatrixStride 16 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%mat4x4 = OpTypeMatrix %v4float 4 +%block = OpTypeStruct %mat4x4 +%block2 = OpTypeStruct %block +%int = OpTypeInt 32 0 +%int_2 = OpConstant %int 2 +%array = OpTypeArray %block2 %int_2 +%ptr_function_array = OpTypePointer Function %array +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_function_array Function +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, InvalidLayoutBufferBlockWorkgroup) { + const std::string spirv = R"( +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %block BufferBlock +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%block = OpTypeStruct %int +%ptr_wg_block = OpTypePointer Workgroup %block +%void_fn = OpTypeFunction %void +%var = OpVariable %ptr_wg_block Workgroup +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Invalid explicit layout decorations on type for operand")); +} + +TEST_F(ValidateDecorations, ExplicitLayoutOnPtrPhysicalStorageBuffer) { + const std::string spirv = R"( +OpCapability PhysicalStorageBufferAddresses +OpCapability Int64 +OpCapability Shader +OpExtension "SPV_KHR_physical_storage_buffer" +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %_ptr_PhysicalStorageBuffer_int ArrayStride 4 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%int = OpTypeInt 32 1 +%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int ; ArrayStride 4 +%Foo = OpTypeStruct %_ptr_PhysicalStorageBuffer_int +%_ptr_Function_Foo = OpTypePointer Function %Foo +%int_0 = OpConstant %int 0 +%_ptr_Function__ptr_PhysicalStorageBuffer_int = OpTypePointer Function %_ptr_PhysicalStorageBuffer_int +%ulong = OpTypeInt 64 0 +%ulong_0 = OpConstant %ulong 0 +%main = OpFunction %void None %void_fn +%entry = OpLabel +%obj = OpVariable %_ptr_Function_Foo Function +%obj_member = OpAccessChain %_ptr_Function__ptr_PhysicalStorageBuffer_int %obj %int_0 +%nullptr = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_int %ulong_0 +OpStore %obj_member %nullptr +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/third_party/spirv-tools/test/val/val_id_test.cpp b/third_party/spirv-tools/test/val/val_id_test.cpp index b4c9d78a8f..a3c63cd7c9 100644 --- a/third_party/spirv-tools/test/val/val_id_test.cpp +++ b/third_party/spirv-tools/test/val/val_id_test.cpp @@ -5764,7 +5764,7 @@ TEST_P(ValidateIdWithMessage, %2 = OpFunction %8 None %11 %4 = OpFunctionParameter %10 %15 = OpLabel -%16 = OpLoad %6 %3 Aligned 0 +%16 = OpLoad %6 %3 Aligned 1 %17 = OpCompositeExtract %5 %16 0 %18 = OpInBoundsPtrAccessChain %13 %4 %17 %12 OpStore %18 %14 Aligned 4 @@ -5798,7 +5798,7 @@ TEST_P(ValidateIdWithMessage, OpPtrAccessChainGood) { %2 = OpFunction %8 None %12 %4 = OpFunctionParameter %11 %17 = OpLabel -%18 = OpLoad %6 %3 Aligned 0 +%18 = OpLoad %6 %3 Aligned 1 %19 = OpCompositeExtract %5 %18 0 %20 = OpBitwiseAnd %5 %19 %13 %21 = OpPtrAccessChain %15 %4 %20 %14 diff --git a/third_party/spirv-tools/test/val/val_interfaces_test.cpp b/third_party/spirv-tools/test/val/val_interfaces_test.cpp index 50f45573f7..2ab887f791 100644 --- a/third_party/spirv-tools/test/val/val_interfaces_test.cpp +++ b/third_party/spirv-tools/test/val/val_interfaces_test.cpp @@ -1415,7 +1415,12 @@ OpFunctionEnd )"; CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08722")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Entry-point has conflicting output location " + "assignment at location 0, component 0")); } TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponentBad) { @@ -1428,7 +1433,7 @@ OpDecorate %11 Location 0 OpDecorate %18 Component 0 OpDecorate %18 Location 0 OpDecorate %28 Component 1 -OpDecorate %28 Location 0 +OpDecorate %28 Location 1 OpDecorate %36 Location 1 OpDecorate %40 Component 1 OpDecorate %40 Location 1 @@ -1543,7 +1548,12 @@ OpFunctionEnd )"; CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Entry-point has conflicting input location " + "assignment at location 0, component 0")); } TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent2) { @@ -1575,7 +1585,12 @@ OpFunctionEnd )"; CompileSuccessfully(text, SPV_ENV_VULKAN_1_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Entry-point has conflicting input location " + "assignment at location 0, component 0")); } TEST_F(ValidateInterfacesTest, DuplicateInterfaceVariableSuccess) { @@ -1668,7 +1683,7 @@ TEST_F(ValidateInterfacesTest, InvalidLocationTypePointer) { HasSubstr("Invalid type to assign a location")); } -TEST_F(ValidateInterfacesTest, ValidLocationTypePhysicalStorageBufferPointer) { +TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointer) { const std::string text = R"( OpCapability Shader OpCapability PhysicalStorageBufferAddresses @@ -1677,10 +1692,10 @@ OpEntryPoint Vertex %main "main" %var OpDecorate %var Location 0 OpDecorate %var RestrictPointer %void = OpTypeVoid -%int = OpTypeInt 32 0 -%ptr = OpTypePointer PhysicalStorageBuffer %int -%ptr2 = OpTypePointer Input %ptr -%var = OpVariable %ptr2 Input +%uint = OpTypeInt 32 0 +%psb_ptr = OpTypePointer PhysicalStorageBuffer %uint +%in_ptr = OpTypePointer Input %psb_ptr +%var = OpVariable %in_ptr Input %void_fn = OpTypeFunction %void %main = OpFunction %void None %void_fn %entry = OpLabel @@ -1688,7 +1703,139 @@ OpReturn OpFunctionEnd )"; CompileSuccessfully(text, SPV_ENV_VULKAN_1_3); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Input-09557")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Input/Output interface variable id <2> contains a " + "PhysicalStorageBuffer pointer, which is not allowed")); +} + +TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerArray) { + const std::string text = R"( + OpCapability Shader + OpCapability PhysicalStorageBufferAddresses + OpMemoryModel PhysicalStorageBuffer64 GLSL450 + OpEntryPoint Vertex %main "main" %var + OpDecorate %var Location 0 + OpDecorate %var RestrictPointer + %void = OpTypeVoid + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 + %psb_ptr = OpTypePointer PhysicalStorageBuffer %uint + %array = OpTypeArray %psb_ptr %uint_3 + %in_ptr = OpTypePointer Input %array + %var = OpVariable %in_ptr Input + %void_fn = OpTypeFunction %void + %main = OpFunction %void None %void_fn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(text, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Input-09557")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Input/Output interface variable id <2> contains a " + "PhysicalStorageBuffer pointer, which is not allowed")); +} +TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerStruct) { + const std::string text = R"( + OpCapability Shader + OpCapability PhysicalStorageBufferAddresses + OpMemoryModel PhysicalStorageBuffer64 GLSL450 + OpEntryPoint Vertex %main "main" %var + OpDecorate %var Location 0 + OpDecorate %var RestrictPointer + %void = OpTypeVoid + %int = OpTypeInt 32 1 + OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer + %struct_0 = OpTypeStruct %int %psb_ptr + %struct_1 = OpTypeStruct %int %int + %psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1 + %in_ptr = OpTypePointer Input %struct_0 + %var = OpVariable %in_ptr Input + %void_fn = OpTypeFunction %void + %main = OpFunction %void None %void_fn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(text, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Input-09557")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Input/Output interface variable id <2> contains a " + "PhysicalStorageBuffer pointer, which is not allowed")); +} + +TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerArrayOfStruct) { + const std::string text = R"( + OpCapability Shader + OpCapability PhysicalStorageBufferAddresses + OpMemoryModel PhysicalStorageBuffer64 GLSL450 + OpEntryPoint Vertex %main "main" %var + OpDecorate %var Location 0 + OpDecorate %var RestrictPointer + %void = OpTypeVoid + %int = OpTypeInt 32 1 + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 + OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer + %array_1 = OpTypeArray %psb_ptr %uint_3 + %struct_0 = OpTypeStruct %int %array_1 + %struct_1 = OpTypeStruct %int %int + %psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1 + %array_0 = OpTypeArray %struct_0 %uint_3 + %in_ptr = OpTypePointer Input %array_0 + %var = OpVariable %in_ptr Input + %void_fn = OpTypeFunction %void + %main = OpFunction %void None %void_fn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(text, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Input-09557")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Input/Output interface variable id <2> contains a " + "PhysicalStorageBuffer pointer, which is not allowed")); +} + +TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerNestedStruct) { + const std::string text = R"( + OpCapability Shader + OpCapability PhysicalStorageBufferAddresses + OpMemoryModel PhysicalStorageBuffer64 GLSL450 + OpEntryPoint Vertex %main "main" %var + OpDecorate %var Location 0 + OpDecorate %var RestrictPointer + %void = OpTypeVoid + %int = OpTypeInt 32 1 + OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer + %struct_0 = OpTypeStruct %int %psb_ptr + %struct_1 = OpTypeStruct %int %int + %psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1 + %struct_2 = OpTypeStruct %int %struct_0 + %in_ptr = OpTypePointer Input %struct_2 + %var = OpVariable %in_ptr Input + %void_fn = OpTypeFunction %void + %main = OpFunction %void None %void_fn + %entry = OpLabel + OpReturn + OpFunctionEnd + )"; + CompileSuccessfully(text, SPV_ENV_VULKAN_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3)); + EXPECT_THAT(getDiagnosticString(), + AnyVUID("VUID-StandaloneSpirv-Input-09557")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Input/Output interface variable id <2> contains a " + "PhysicalStorageBuffer pointer, which is not allowed")); } TEST_F(ValidateInterfacesTest, UntypedVariableInputMissing) { diff --git a/third_party/spirv-tools/test/val/val_memory_test.cpp b/third_party/spirv-tools/test/val/val_memory_test.cpp index 8855b0ef6d..4a31035c79 100644 --- a/third_party/spirv-tools/test/val/val_memory_test.cpp +++ b/third_party/spirv-tools/test/val/val_memory_test.cpp @@ -1818,6 +1818,68 @@ OpFunctionEnd HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned")); } +TEST_F(ValidateMemory, PSBStoreAlignedZero) { + const std::string body = R"( +OpCapability PhysicalStorageBufferAddresses +OpCapability Shader +OpExtension "SPV_EXT_physical_storage_buffer" +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%ptr = OpTypePointer PhysicalStorageBuffer %uint +%pptr_f = OpTypePointer Function %ptr +%void = OpTypeVoid +%voidfn = OpTypeFunction %void +%main = OpFunction %void None %voidfn +%entry = OpLabel +%val1 = OpVariable %pptr_f Function +%val2 = OpLoad %ptr %val1 +OpStore %val2 %uint_1 Aligned 0 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Memory accesses Aligned operand value 0 is not a power of two")); +} + +TEST_F(ValidateMemory, PSBStoreAlignedNonPoT) { + const std::string body = R"( +OpCapability PhysicalStorageBufferAddresses +OpCapability Shader +OpExtension "SPV_EXT_physical_storage_buffer" +OpMemoryModel PhysicalStorageBuffer64 GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%ptr = OpTypePointer PhysicalStorageBuffer %uint +%pptr_f = OpTypePointer Function %ptr +%void = OpTypeVoid +%voidfn = OpTypeFunction %void +%main = OpFunction %void None %voidfn +%entry = OpLabel +%val1 = OpVariable %pptr_f Function +%val2 = OpLoad %ptr %val1 +OpStore %val2 %uint_1 Aligned 3 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2); + ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "Memory accesses Aligned operand value 3 is not a power of two.")); +} + TEST_F(ValidateMemory, PSBCopyMemoryAlignedSuccess) { const std::string body = R"( OpCapability PhysicalStorageBufferAddresses @@ -3883,8 +3945,11 @@ OpMemoryModel Logical GLSL450 %void = OpTypeVoid %bool = OpTypeBool %int = OpTypeInt 32 0 +%float = OpTypeFloat 32 %ptr_int = OpTypePointer Private %int %var = OpVariable %ptr_int Private +%ptr_float = OpTypePointer Private %float +%var2 = OpVariable %ptr_float Private %func_ty = OpTypeFunction %void %func = OpFunction %void None %func_ty %1 = OpLabel @@ -3897,7 +3962,7 @@ OpMemoryModel Logical GLSL450 spirv += " %bool "; } - spirv += R"(%var %ld + spirv += R"(%var %var2 OpReturn OpFunctionEnd )"; @@ -3908,6 +3973,223 @@ OpFunctionEnd HasSubstr("The types of Operand 1 and Operand 2 must match")); } +TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameType) { + const std::string operation = GetParam(); + + std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointersStorageBuffer +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%ptr = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr StorageBuffer +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +%equal = )" + operation; + + if (operation == "OpPtrDiff") { + spirv += " %int "; + } else { + spirv += " %bool "; + } + + spirv += R"(%var %var +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); +} + +TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameStorageClass) { + const std::string operation = GetParam(); + + std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointersStorageBuffer +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%ptr1 = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr1 StorageBuffer +%ptr2 = OpTypeUntypedPointerKHR StorageBuffer +%var2 = OpUntypedVariableKHR %ptr2 StorageBuffer +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +%equal = )" + operation; + + if (operation == "OpPtrDiff") { + spirv += " %int "; + } else { + spirv += " %bool "; + } + + spirv += R"(%var %var2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + if (operation == "OpPtrDiff") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The types of Operand 1 and Operand 2 must match")); + } else { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + } +} + +TEST_P(ValidatePointerComparisons, BadUntypedPointerDiffStorageClass) { + const std::string operation = GetParam(); + + std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%ptr1 = OpTypeUntypedPointerKHR StorageBuffer +%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer +%ptr2 = OpTypeUntypedPointerKHR Workgroup +%var2 = OpUntypedVariableKHR %ptr2 Workgroup %int +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +%equal = )" + operation; + + if (operation == "OpPtrDiff") { + spirv += " %int "; + } else { + spirv += " %bool "; + } + + spirv += R"(%var1 %var2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + if (operation == "OpPtrDiff") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The types of Operand 1 and Operand 2 must match")); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Pointer storage classes must match")); + } +} + +TEST_P(ValidatePointerComparisons, GoodMixedPointerSameStorageClass) { + const std::string operation = GetParam(); + + std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointersStorageBuffer +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%ptr1 = OpTypeUntypedPointerKHR StorageBuffer +%var = OpUntypedVariableKHR %ptr1 StorageBuffer +%ptr2 = OpTypePointer StorageBuffer %int +%var2 = OpVariable %ptr2 StorageBuffer +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +%equal = )" + operation; + + if (operation == "OpPtrDiff") { + spirv += " %int "; + } else { + spirv += " %bool "; + } + + spirv += R"(%var %var2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + if (operation == "OpPtrDiff") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The types of Operand 1 and Operand 2 must match")); + } else { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + } +} + +TEST_P(ValidatePointerComparisons, BadMixedPointerDiffStorageClass) { + const std::string operation = GetParam(); + + std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability VariablePointers +OpCapability UntypedPointersKHR +OpExtension "SPV_KHR_untyped_pointers" +OpMemoryModel Logical GLSL450 +%void = OpTypeVoid +%bool = OpTypeBool +%int = OpTypeInt 32 0 +%ptr1 = OpTypeUntypedPointerKHR StorageBuffer +%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer +%ptr2 = OpTypePointer Workgroup %int +%var2 = OpVariable %ptr2 Workgroup +%func_ty = OpTypeFunction %void +%func = OpFunction %void None %func_ty +%1 = OpLabel +%equal = )" + operation; + + if (operation == "OpPtrDiff") { + spirv += " %int "; + } else { + spirv += " %bool "; + } + + spirv += R"(%var1 %var2 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4); + if (operation == "OpPtrDiff") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("The types of Operand 1 and Operand 2 must match")); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Pointer storage classes must match")); + } +} + INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons, Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff")); diff --git a/third_party/spirv-tools/tools/opt/opt.cpp b/third_party/spirv-tools/tools/opt/opt.cpp index 07a57252d4..64abf70e8b 100644 --- a/third_party/spirv-tools/tools/opt/opt.cpp +++ b/third_party/spirv-tools/tools/opt/opt.cpp @@ -455,9 +455,9 @@ Options (in lexicographical order):)", instructions.)"); printf(R"( --remove-unused-interface-variables - Removes variables referenced on the |OpEntryPoint| instruction - that are not referenced in the entry point function or any function - in its call tree. Note that this could cause the shader interface + Removes variables referenced on the |OpEntryPoint| instruction + that are not referenced in the entry point function or any function + in its call tree. Note that this could cause the shader interface to no longer match other shader stages.)"); printf(R"( --replace-invalid-opcode @@ -465,6 +465,12 @@ Options (in lexicographical order):)", but not for the current shader stage. To have an effect, all entry points must have the same execution model.)"); printf(R"( + --resolve-binding-conflicts + Renumber bindings to avoid conflicts. + When an image and sampler share the same desriptor set and binding, + increment the binding number of the sampler. Recursively ripple + to higher-numbered bindings until all conflicts resolved resolved.)"); + printf(R"( --ssa-rewrite Replace loads and stores to function local variables with operations on SSA IDs.)"); @@ -501,6 +507,11 @@ Options (in lexicographical order):)", is invalid, the optimizer may fail or generate incorrect code. This options should be used rarely, and with caution.)"); printf(R"( + --split-combined-image-sampler + Replace combined image sampler variables and parameters into + pairs of images and samplers. New variables have the same + bindings as the original variable.)"); + printf(R"( --strength-reduction Replaces instructions with equivalent and less expensive ones.)"); printf(R"( diff --git a/third_party/spirv-tools/tools/util/flags.cpp b/third_party/spirv-tools/tools/util/flags.cpp index 6198e4ba28..9c99677c81 100644 --- a/third_party/spirv-tools/tools/util/flags.cpp +++ b/third_party/spirv-tools/tools/util/flags.cpp @@ -15,6 +15,7 @@ #include "flags.h" #include +#include #include #include #include diff --git a/third_party/spirv-tools/tools/val/val.cpp b/third_party/spirv-tools/tools/val/val.cpp index 3dbc7f4a52..dddfeaed8c 100644 --- a/third_party/spirv-tools/tools/val/val.cpp +++ b/third_party/spirv-tools/tools/val/val.cpp @@ -68,6 +68,8 @@ Options: be allowed by the target environment. --allow-offset-texture-operand Allow use of the Offset texture operands where it would otherwise not be allowed by the target environment. + --allow-vulkan-32-bit-bitwise Allow use of non-32 bit for the Base operand where it would otherwise + not be allowed by the target environment. --before-hlsl-legalization Allows code patterns that are intended to be fixed by spirv-opt's legalization passes. --version Display validator version information. @@ -165,6 +167,8 @@ int main(int argc, char** argv) { options.SetAllowLocalSizeId(true); } else if (0 == strcmp(cur_arg, "--allow-offset-texture-operand")) { options.SetAllowOffsetTextureOperand(true); + } else if (0 == strcmp(cur_arg, "--allow-vulkan-32-bit-bitwise")) { + options.SetAllowVulkan32BitBitwise(true); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { options.SetRelaxStructStore(true); } else if (0 == cur_arg[1]) { diff --git a/third_party/spirv-tools/utils/check_code_format.sh b/third_party/spirv-tools/utils/check_code_format.sh index da5e019859..80f94af0f3 100755 --- a/third_party/spirv-tools/utils/check_code_format.sh +++ b/third_party/spirv-tools/utils/check_code_format.sh @@ -12,14 +12,33 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# -# Script to determine if source code in Pull Request is properly formatted. + +# This script determines if the source code in a Pull Request is properly formatted. # Exits with non 0 exit code if formatting is needed. -# -# This script assumes to be invoked at the project root directory. +# Assumptions: +# - git and python3 are on the path +# - Runs from the project root diretory. +# - 'clang-format' is on the path, or env var CLANG_FORMAT points to it. +# - 'clang-format-diff.py' is in the utils directory, or env var +# points to it.CLANG_FORMAT_DIFF BASE_BRANCH=${1:-main} +CLANG_FORMAT=${CLANG_FORMAT:-clang-format} +if [ ! -f "$CLANG_FORMAT" ]; then + echo missing clang-format: set CLANG_FORMAT or put clang-format in the PATH + exit 1 +fi + +# Find clang-format-diff.py from an environment variable, or use a default +CLANG_FORMAT_DIFF=${CLANG_FORMAT_DIFF:-./utils/clang-format-diff.py} +if [ ! -f "$CLANG_FORMAT_DIFF" ]; then + echo missing clang-format-diffy.py: set CLANG_FORMAT_DIFF or put it in ./utils/clang-format-diff.py + exit 1 +fi + +echo "Comparing "$(git rev-parse HEAD)" against $BASE_BRANCH" + FILES_TO_CHECK=$(git diff --name-only ${BASE_BRANCH} | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$") if [ -z "${FILES_TO_CHECK}" ]; then @@ -27,7 +46,7 @@ if [ -z "${FILES_TO_CHECK}" ]; then exit 0 fi -FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python ./utils/clang-format-diff.py -p1 -style=file) +FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python3 "${CLANG_FORMAT_DIFF}" -p1 -style=file -binary "$CLANG_FORMAT") if [ -z "${FORMAT_DIFF}" ]; then echo "All source code in PR properly formatted."