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..3f1f805b56 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': '9f5fc9d1e934571a1f713b5a00cd5f15db8c71ed',
- 'effcee_revision': '12241cbc30f20730b656db7fd5a3fa36cd420843',
+ 'effcee_revision': '874b47102c57a8979c0f154cf8e0eab53c0a0502',
- 'googletest_revision': 'e235eb34c6c4fed790ccdad4b16394301360dcd4',
+ 'googletest_revision': '3af834740f58ef56058e6f8a1be51f62bc3a6515',
# 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': '0e710677989b4326ac974fd80c5308191ed80965',
}
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/fold.cpp b/third_party/spirv-tools/source/opt/fold.cpp
index 942da6835f..cd070c7402 100644
--- a/third_party/spirv-tools/source/opt/fold.cpp
+++ b/third_party/spirv-tools/source/opt/fold.cpp
@@ -50,7 +50,7 @@ uint32_t InstructionFolder::UnaryOperate(spv::Op opcode,
if (s_operand == std::numeric_limits::min()) {
return s_operand;
}
- return -s_operand;
+ return static_cast(-s_operand);
}
case spv::Op::OpNot:
return ~operand;
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..0af4dba6cb
--- /dev/null
+++ b/third_party/spirv-tools/source/opt/split_combined_image_sampler_pass.cpp
@@ -0,0 +1,648 @@
+// 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());
+
+ // Move decorations, such as RelaxedPrecision.
+ auto* deco_mgr = context()->get_decoration_mgr();
+ deco_mgr->CloneDecorations(load->result_id(), image->result_id());
+ deco_mgr->CloneDecorations(load->result_id(), sampler->result_id());
+ deco_mgr->RemoveDecorationsFrom(load->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/opt/value_number_table.cpp b/third_party/spirv-tools/source/opt/value_number_table.cpp
index 743dc52bb1..0c7e575e7f 100644
--- a/third_party/spirv-tools/source/opt/value_number_table.cpp
+++ b/third_party/spirv-tools/source/opt/value_number_table.cpp
@@ -45,28 +45,43 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
return value;
}
+ auto assign_new_number = [this](Instruction* i) {
+ const auto new_value = TakeNextValueNumber();
+ id_to_value_[i->result_id()] = new_value;
+ return new_value;
+ };
+
// If the instruction has other side effects, then it must
// have its own value number.
- // OpSampledImage and OpImage must remain in the same basic block in which
- // they are used, because of this we will assign each one it own value number.
if (!context()->IsCombinatorInstruction(inst) &&
!inst->IsCommonDebugInstr()) {
- value = TakeNextValueNumber();
- id_to_value_[inst->result_id()] = value;
- return value;
+ return assign_new_number(inst);
}
+ // OpSampledImage and OpImage must remain in the same basic block in which
+ // they are used, because of this we will assign each one it own value number.
switch (inst->opcode()) {
case spv::Op::OpSampledImage:
case spv::Op::OpImage:
case spv::Op::OpVariable:
- value = TakeNextValueNumber();
- id_to_value_[inst->result_id()] = value;
- return value;
+ return assign_new_number(inst);
default:
break;
}
+ // A load that yields an image, sampler, or sampled image must remain in
+ // the same basic block. So assign it its own value number.
+ if (inst->IsLoad()) {
+ switch (context()->get_def_use_mgr()->GetDef(inst->type_id())->opcode()) {
+ case spv::Op::OpTypeSampledImage:
+ case spv::Op::OpTypeImage:
+ case spv::Op::OpTypeSampler:
+ return assign_new_number(inst);
+ default:
+ break;
+ }
+ }
+
// If it is a load from memory that can be modified, we have to assume the
// memory has been modified, so we give it a new value number.
//
@@ -74,9 +89,7 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) {
// read only. However, if this is ever relaxed because we analyze stores, we
// will have to add a new case for volatile loads.
if (inst->IsLoad() && !inst->IsReadOnlyLoad()) {
- value = TakeNextValueNumber();
- id_to_value_[inst->result_id()] = value;
- return value;
+ return assign_new_number(inst);
}
analysis::DecorationManager* dec_mgr = context()->get_decoration_mgr();
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..a576525e72 100644
--- a/third_party/spirv-tools/source/val/validate_decorations.cpp
+++ b/third_party/spirv-tools/source/val/validate_decorations.cpp
@@ -509,6 +509,15 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
// Check offset.
if (offset == 0xffffffff)
return fail(memberIdx) << "is missing an Offset decoration";
+
+ if (opcode == spv::Op::OpTypeRuntimeArray &&
+ ordered_member_idx != member_offsets.size() - 1) {
+ return vstate.diag(SPV_ERROR_INVALID_ID, vstate.FindDef(struct_id))
+ << vstate.VkErrorID(4680) << "Structure id " << struct_id
+ << " has a runtime array at offset " << offset
+ << ", but other members at larger offsets";
+ }
+
if (!scalar_block_layout && relaxed_block_layout &&
opcode == spv::Op::OpTypeVector) {
// In relaxed block layout, the vector offset must be aligned to the
@@ -1916,7 +1925,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 +2092,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 +2301,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..795ecebbbb 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,20 @@ 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.
+ if (auto error = NumConsumedLocations(
+ _, _.FindDef(type->GetOperandAs(1)), num_locations)) {
+ return error;
+ }
*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 +247,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 +354,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 +411,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 +496,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/validate_memory_semantics.cpp b/third_party/spirv-tools/source/val/validate_memory_semantics.cpp
index dab7b5a194..d94d37dbf4 100644
--- a/third_party/spirv-tools/source/val/validate_memory_semantics.cpp
+++ b/third_party/spirv-tools/source/val/validate_memory_semantics.cpp
@@ -66,8 +66,7 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(opcode)
<< ": Memory Semantics can have at most one of the following "
- "bits "
- "set: Acquire, Release, AcquireRelease or "
+ "bits set: Acquire, Release, AcquireRelease or "
"SequentiallyConsistent";
}
@@ -176,10 +175,8 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< _.VkErrorID(4732) << spvOpcodeString(opcode)
<< ": Vulkan specification requires Memory Semantics to have "
- "one "
- "of the following bits set: Acquire, Release, "
- "AcquireRelease "
- "or SequentiallyConsistent";
+ "one of the following bits set: Acquire, Release, "
+ "AcquireRelease or SequentiallyConsistent";
} else if (opcode != spv::Op::OpMemoryBarrier &&
num_memory_order_set_bits) {
// should leave only atomics and control barriers for Vulkan env
@@ -203,11 +200,20 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _,
"storage class";
}
- if (opcode == spv::Op::OpControlBarrier && value && !includes_storage_class) {
- return _.diag(SPV_ERROR_INVALID_DATA, inst)
- << _.VkErrorID(4650) << spvOpcodeString(opcode)
- << ": expected Memory Semantics to include a Vulkan-supported "
- "storage class if Memory Semantics is not None";
+ if (opcode == spv::Op::OpControlBarrier && value) {
+ if (!num_memory_order_set_bits) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << _.VkErrorID(10609) << spvOpcodeString(opcode)
+ << ": Vulkan specification requires non-zero Memory Semantics "
+ "to have one of the following bits set: Acquire, Release, "
+ "AcquireRelease or SequentiallyConsistent";
+ }
+ if (!includes_storage_class) {
+ return _.diag(SPV_ERROR_INVALID_DATA, inst)
+ << _.VkErrorID(4650) << spvOpcodeString(opcode)
+ << ": expected Memory Semantics to include a Vulkan-supported "
+ "storage class if Memory Semantics is not None";
+ }
}
}
diff --git a/third_party/spirv-tools/source/val/validation_state.cpp b/third_party/spirv-tools/source/val/validation_state.cpp
index 4604d6da51..6c0f2dbc91 100644
--- a/third_party/spirv-tools/source/val/validation_state.cpp
+++ b/third_party/spirv-tools/source/val/validation_state.cpp
@@ -2311,6 +2311,8 @@ std::string ValidationState_t::VkErrorID(uint32_t id,
return VUID_WRAP(VUID-StandaloneSpirv-None-04644);
case 4645:
return VUID_WRAP(VUID-StandaloneSpirv-None-04645);
+ case 10609:
+ return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-10609);
case 4650:
return VUID_WRAP(VUID-StandaloneSpirv-OpControlBarrier-04650);
case 4651:
@@ -2415,8 +2417,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 +2525,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 +2536,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