Compare commits

...
99 changed files with 8559 additions and 359 deletions
+5
View File
@@ -88,7 +88,12 @@ void minLeftMaxRight(inout vec4 a, inout vec4 b, inout vec4 c, inout vec4 d, ino
// - we can't use a function because offset must be a constant expression
// - we can't use line continuation because that's not supported on desktop
#ifdef TARGET_WEBGPU_ENVIRONMENT
//WebGPU can not handle texelFetchOffset without an error about "ConstOffset is only permitted...".
#define makeSample(ij, o) vec4(texelFetch(materialParams_dof, ij+o, 0).rgb, texelFetch(materialParams_alpha, ij+o, 0).r)
#else
#define makeSample(ij, o) vec4(texelFetchOffset(materialParams_dof, ij, 0, o).rgb, texelFetchOffset(materialParams_alpha, ij, 0, o).r)
#endif
vec4 median(ivec2 ij) {
vec4 v[9];
+98 -19
View File
@@ -517,29 +517,104 @@ void GLSLPostProcessor::spirvToMsl(const SpirvBlob* spirv, std::string* outMsl,
MetalArgumentBuffer::destroy(&argBuffer);
}
}
void GLSLPostProcessor::rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv) {
constexpr size_t HEADER_SIZE = 5;
size_t const dataSize = spirv.size();
uint32_t* data = spirv.data();
struct Descriptor {
uint32_t binding = 0xFFFFFFFF;
bool fixed = false;
};
std::unordered_map<uint32_t, Descriptor> descriptors;
std::set<uint32_t> unidentifiedLoadTargets;
std::vector<uint32_t> bindingTargets;
auto getDesc = [&](uint32_t var) -> Descriptor& {
using itertype = std::unordered_map<uint32_t, Descriptor>::iterator;
itertype itr;
if (itr = descriptors.find(var); itr == descriptors.end()) {
itr = descriptors.insert({var, {}}).first;
}
return itr->second;
};
auto pass = [&](uint32_t targetOp, std::function<void(uint32_t)> f) {
for (uint32_t cursor = HEADER_SIZE, cursorEnd = dataSize; cursor < cursorEnd;) {
uint32_t const firstWord = data[cursor];
uint32_t const wordCount = firstWord >> 16;
uint32_t const op = firstWord & 0x0000FFFF;
if (targetOp == op) {
f(cursor+1);
}
cursor += wordCount;
}
};
uint32_t samplerTypeId = 0;
pass(spv::Op::OpName, [&](uint32_t pos) {
auto target = data[pos];
char* name = (char*)&data[pos+1];
std::string_view view(name);
if (view.find("_sampler") != std::string_view::npos) {
unidentifiedLoadTargets.insert(target);
}
});
// Write out the offsetted bindings
pass(spv::Op::OpDecorate, [&](uint32_t pos) {
uint32_t const type = data[pos + 1];
if (type == spv::Decoration::DecorationBinding) {
uint32_t const targetVar = data[pos];
if (unidentifiedLoadTargets.find(targetVar) != unidentifiedLoadTargets.end()) {
data[pos + 2] = data[pos + 2]*2 + 1;
}
else {
data[pos + 2] = data[pos + 2] *2;
}
}
});
}
bool GLSLPostProcessor::spirvToWgsl(SpirvBlob *spirv, std::string *outWsl) {
#if FILAMENT_SUPPORTS_WEBGPU
// We need to remove dead code for our variant-filter workaround for WebGPU to work
// This is especially relevant for removing the push constants that morphing uses when not disabled
spv::spirvbin_t remapper(0);
remapper.remap(*spirv, spv::spirvbin_base_t::DCE_ALL);
//We need to run some opt-passes at all times to transpile to WGSL
auto optimizer = createEmptyOptimizer();
optimizer->RegisterPass(CreateSplitCombinedImageSamplerPass());
//optimizer->RegisterPass(CreateResolveBindingConflictsPass());
//Currently no options we want to use
const tint::spirv::reader::Options readerOpts{};
optimizeSpirv(optimizer, *spirv);
//After splitting the image samplers, we need to remap the bindings to separate them.
//TODO: This has issues, so use CreateResolveBindingConflictsPass instead. Mapping is less predictable but short term
rebindImageSamplerForWGSL(*spirv);
//Allow non-uniform derivitives due to our nested shaders. See https://github.com/gpuweb/gpuweb/issues/3479
const tint::spirv::reader::Options readerOpts{true};
tint::wgsl::writer::Options writerOpts{};
tint::Program tintRead = tint::spirv::reader::Read(*spirv, readerOpts);
if (tintRead.Diagnostics().ContainsErrors()) {
//TODO remove this if block once combined image sampler conversion works.
if (tintRead.Diagnostics().Str().rfind("error: WGSL does not support combined image-samplers") != std::string::npos) {
slog.w << "This tint reader error is currently ignored during WebGPU bringup: " << tintRead.Diagnostics().Str() << io::endl;
}
else {
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
return false;
}
//We know errors can potentially crop up, and want the ability to ignore them if needed for sample bringup
#ifndef FILAMENT_WEBGPU_IGNORE_TNT_READ_ERRORS
slog.e << "Tint Reader Error: " << tintRead.Diagnostics().Str() << io::endl;
spv_context context = spvContextCreate(SPV_ENV_VULKAN_1_1_SPIRV_1_4);
spv_text text = nullptr;
spv_diagnostic diagnostic = nullptr;
spv_result_t result = spvBinaryToText(
context,
spirv->data(),
spirv->size(),
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_COLOR,
&text,
&diagnostic);
slog.e << "Beginning SpirV-output dump\n\n" << text->str << "\n\nEndSPIRV\n" << io::endl;
spvTextDestroy(text);
return false;
#endif
}
tint::Result<tint::wgsl::writer::Output> wgslOut = tint::wgsl::writer::Generate(tintRead,writerOpts);
@@ -887,10 +962,8 @@ bool GLSLPostProcessor::fullOptimization(const TShader& tShader,
return true;
}
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
MaterialBuilder::Optimization optimization, Config const& config) {
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createEmptyOptimizer() {
auto optimizer = std::make_shared<spvtools::Optimizer>(SPV_ENV_UNIVERSAL_1_3);
optimizer->SetMessageConsumer([](spv_message_level_t level,
const char* source, const spv_position_t& position, const char* message) {
if (!filterSpvOptimizerMessage(level)) {
@@ -899,6 +972,12 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
slog.e << stringifySpvOptimizerMessage(level, source, position, message)
<< io::endl;
});
return optimizer;
}
std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
MaterialBuilder::Optimization optimization, Config const& config) {
auto optimizer = createEmptyOptimizer();
if (optimization == MaterialBuilder::Optimization::SIZE) {
// When optimizing for size, we don't run the SPIR-V through any size optimization passes
@@ -923,7 +1002,7 @@ std::shared_ptr<spvtools::Optimizer> GLSLPostProcessor::createOptimizer(
return optimizer;
}
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const {
void GLSLPostProcessor::optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) {
if (!optimizer->Run(spirv.data(), spirv.size(), &spirv)) {
slog.e << "SPIR-V optimizer pass failed" << io::endl;
return;
@@ -977,7 +1056,7 @@ void GLSLPostProcessor::registerPerformancePasses(Optimizer& optimizer, Config c
RegisterPass(CreateWrapOpKillPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL);
RegisterPass(CreateMergeReturnPass(), MaterialBuilder::TargetApi::METAL | MaterialBuilder::TargetApi::WEBGPU);
RegisterPass(CreateInlineExhaustivePass());
RegisterPass(CreateAggressiveDCEPass());
RegisterPass(CreatePrivateToLocalPass());
+7 -1
View File
@@ -114,14 +114,20 @@ private:
* Retrieve an optimizer instance tuned for the given optimization level and shader configuration.
*/
using OptimizerPtr = std::shared_ptr<spvtools::Optimizer>;
static OptimizerPtr createOptimizer(
MaterialBuilder::Optimization optimization,
Config const& config);
static OptimizerPtr createEmptyOptimizer();
static void registerSizePasses(spvtools::Optimizer& optimizer, Config const& config);
static void registerPerformancePasses(spvtools::Optimizer& optimizer, Config const& config);
void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob& spirv) const;
static void optimizeSpirv(OptimizerPtr optimizer, SpirvBlob &spirv);
static void rebindImageSamplerForWGSL(std::vector<uint32_t>& spirv);
void fixupClipDistance(SpirvBlob& spirv, GLSLPostProcessor::Config const& config) const;
@@ -937,6 +937,15 @@ utils::io::sstream& CodeGenerator::generateSpecializationConstant(utils::io::sst
std::string const constantString = std::visit(SpecializationConstantFormatter(), value);
static const char* types[] = { "int", "float", "bool" };
// Spec constants aren't fully supported in Tint,
// workaround until https://issues.chromium.org/issues/42250586 is resolved
if (mTargetApi == TargetApi::WEBGPU) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << "_hack = " << constantString << ";\n"
<< types[value.index()] << " " << name << " = " << name << "_hack;\n";
return out;
}
if (mTargetLanguage == MaterialBuilderBase::TargetLanguage::SPIRV) {
out << "layout (constant_id = " << id << ") const "
<< types[value.index()] << " " << name << " = " << constantString << ";\n";
+3 -3
View File
@@ -129,14 +129,14 @@ bool isDoubleSided() {
/**
* Returns the cascade index for this fragment (between 0 and CONFIG_MAX_SHADOW_CASCADES - 1).
*/
int getShadowCascade() {
uint getShadowCascade() {
highp float z = mulMat4x4Float3(getViewFromWorldMatrix(), getWorldPosition()).z;
ivec4 greaterZ = ivec4(greaterThan(frameUniforms.cascadeSplits, vec4(z)));
int cascadeCount = frameUniforms.cascades & 0xF;
uint cascadeCount = frameUniforms.cascades & 0xF;
return clamp(greaterZ.x + greaterZ.y + greaterZ.z + greaterZ.w, 0, cascadeCount - 1);
}
highp vec4 getCascadeLightSpacePosition(int cascade) {
highp vec4 getCascadeLightSpacePosition(uint cascade) {
// For the first cascade, return the interpolated light space position.
// This branch will be coherent (mostly) for neighboring fragments, and it's worth avoiding
// the matrix multiply inside computeLightSpacePosition.
+2 -2
View File
@@ -52,12 +52,12 @@ void evaluateDirectionalLight(const MaterialInputs material,
if (light.NoL > 0.0) {
float ssContactShadowOcclusion = 0.0;
int cascade = getShadowCascade();
uint cascade = getShadowCascade();
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1 << cascade) << 8));
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1);
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
highp vec4 shadowPosition = getShadowPosition(cascade);
visibility = shadow(true, sampler0_shadowMap, cascade, shadowPosition, 0.0);
visibility = shadow(true, sampler0_shadowMap, int(cascade), shadowPosition, 0.0);
#if defined(MATERIAL_HAS_SHADOW_STRENGTH)
applyShadowStrength(visibility, material.shadowStrength);
#endif
+1 -1
View File
@@ -41,7 +41,7 @@ vec4 evaluateMaterial(const MaterialInputs material) {
#if defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
#if defined(VARIANT_HAS_SHADOWING)
float visibility = 1.0;
int cascade = getShadowCascade();
uint cascade = getShadowCascade();
bool cascadeHasVisibleShadows = bool(frameUniforms.cascades & ((1 << cascade) << 8));
bool hasDirectionalShadows = bool(frameUniforms.directionalShadows & 1);
if (hasDirectionalShadows && cascadeHasVisibleShadows) {
+1 -1
View File
@@ -485,7 +485,7 @@ float ShadowSample_VSM(const bool ELVSM, const highp sampler2DArray shadowMap,
// get texture coordinate for directional and spot shadow maps
#if defined(VARIANT_HAS_DIRECTIONAL_LIGHTING)
highp vec4 getShadowPosition(const int cascade) {
highp vec4 getShadowPosition(const uint cascade) {
return getCascadeLightSpacePosition(cascade);
}
#endif
+2
View File
@@ -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 \
+1
View File
@@ -201,6 +201,7 @@ cc_library(
deps = [
":spirv_tools_internal",
"@spirv_headers//:spirv_common_headers",
"@spirv_headers//:spirv_c_headers",
],
)
+5
View File
@@ -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",
+35
View File
@@ -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)
+5 -5
View File
@@ -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 = {
+44
View File
@@ -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
<a name="op-unknown"></a>
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>, <WordCount>) <operand 1> ...
```
`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
@@ -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);
@@ -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.
//
+56 -2
View File
@@ -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> impl_; // Unique pointer to internal data.
std::unique_ptr<Impl> 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_
+26
View File
@@ -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.
+9 -30
View File
@@ -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}"
+1 -1
View File
@@ -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
+57 -5
View File
@@ -67,7 +67,9 @@ void CompactIds(std::vector<uint32_t>& 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 <typename T>
void Differ::GroupIds(const IdGroup& ids, bool is_src,
std::map<T, IdGroup>* 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 <typename T>
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<uint32_t>(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<uint32_t>(
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,
+4 -2
View File
@@ -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);
}
}
+4
View File
@@ -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
@@ -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);
+1 -1
View File
@@ -50,7 +50,7 @@ uint32_t InstructionFolder::UnaryOperate(spv::Op opcode,
if (s_operand == std::numeric_limits<int32_t>::min()) {
return s_operand;
}
return -s_operand;
return static_cast<uint32_t>(-s_operand);
}
case spv::Op::OpNot:
return ~operand;
+167 -19
View File
@@ -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<const analysis::Constant*>& 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<uint32_t> 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<const analysis::Constant*>& 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<const analysis::Constant*>& constants) {
assert(constants.size() == 2);
uint32_t operand = std::numeric_limits<uint32_t>::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<uint32_t>::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<const analysis::Constant*>& 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<const analysis::Constant*>& 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());
+20 -1
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
#include <string>
#include <unordered_set>
@@ -39,6 +40,7 @@ class Function {
public:
using iterator = UptrVectorIterator<BasicBlock>;
using const_iterator = UptrVectorIterator<BasicBlock, true>;
using ParamList = std::vector<std::unique_ptr<Instruction>>;
// 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<void(
std::unique_ptr<Instruction>&&, std::back_insert_iterator<ParamList>&)>;
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<Instruction> end_inst);
@@ -197,7 +216,7 @@ class Function {
// The OpFunction instruction that begins the definition of this function.
std::unique_ptr<Instruction> def_inst_;
// All parameters to this function.
std::vector<std::unique_ptr<Instruction>> 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
@@ -55,6 +55,11 @@ Pass::Status InlineExhaustivePass::InlineExhaustive(Function* func) {
}
}
}
if (modified) {
FixDebugDeclares(func);
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
}
+65
View File
@@ -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<uint32_t, Instruction*> access_chains;
std::vector<Instruction*> 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<uint32_t, Instruction*>& 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<Operand> 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
+11
View File
@@ -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<uint32_t, Function*> id2function_;
@@ -241,6 +247,11 @@ class InlinePass : public Pass {
// structural dominance.
void UpdateSingleBlockLoopContinueTarget(
uint32_t new_id, std::vector<std::unique_ptr<BasicBlock>>* 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<uint32_t, Instruction*>& access_chains);
};
} // namespace opt
+67 -6
View File
@@ -15,6 +15,7 @@
#ifndef SOURCE_OPT_IR_BUILDER_H_
#define SOURCE_OPT_IR_BUILDER_H_
#include <cassert>
#include <limits>
#include <memory>
#include <utility>
@@ -480,8 +481,11 @@ class InstructionBuilder {
return AddInstruction(std::move(select));
}
Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id,
std::vector<uint32_t> ids) {
Instruction* AddOpcodeAccessChain(spv::Op opcode, uint32_t type_id,
uint32_t base_ptr_id,
const std::vector<uint32_t>& ids) {
assert(opcode == spv::Op::OpAccessChain ||
opcode == spv::Op::OpInBoundsAccessChain);
std::vector<Operand> 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<Instruction> new_inst(
new Instruction(GetContext(), spv::Op::OpAccessChain, type_id,
GetContext()->TakeNextId(), operands));
std::unique_ptr<Instruction> 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<uint32_t>& 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<uint32_t>& 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<Operand> 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<Operand> operands{{SPV_OPERAND_TYPE_ID, {value_id}}};
// TODO(1841): Handle id overflow.
std::unique_ptr<Instruction> 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<Operand> operands;
operands.push_back({SPV_OPERAND_TYPE_ID, {storage_class}});
operands.push_back({SPV_OPERAND_TYPE_STORAGE_CLASS, {storage_class}});
std::unique_ptr<Instruction> 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<uint32_t>& literals) {
std::vector<Operand> 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<Instruction> 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<uint32_t>& 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<Operand> 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<Instruction> 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<Instruction>&& insn) {
Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
+1
View File
@@ -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,
+17 -3
View File
@@ -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<opt::OpExtInstWithForwardReferenceFixupPass>());
}
Optimizer::PassToken CreateSplitCombinedImageSamplerPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::SplitCombinedImageSamplerPass>());
}
Optimizer::PassToken CreateResolveBindingConflictsPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::ResolveBindingConflictsPass>());
}
} // namespace spvtools
extern "C" {
+2
View File
@@ -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"
@@ -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 <algorithm>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#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<VarBindingInfo*>;
// 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<uint32_t, BindingList>;
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<uint32_t>& CalleesBeforeCallers() const {
return visit_order_;
}
// Returns the list functions called from a given function.
const std::unordered_set<uint32_t>& Callees(uint32_t caller) {
return calls_[caller];
}
CallGraph(IRContext& context) {
// Populate calls_.
std::queue<uint32_t> 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<uint32_t, std::unordered_set<uint32_t>> calls_;
// IDs of visited functions;
std::unordered_set<uint32_t> visited_;
// IDs of functions, where callees precede callers.
std::vector<uint32_t> visit_order_;
};
// Returns vector binding info for all resource variables in the module.
auto GetVarBindings(IRContext& context) {
std::vector<VarBindingInfo> 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<spv::Decoration>(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<uint32_t, DescriptorSets> used_vars;
// Determine variables directly used by functions.
auto* def_use_mgr = context()->get_def_use_mgr();
for (auto& var : vars) {
std::unordered_set<uint32_t> 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<BindingList*> 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
@@ -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 <unordered_map>
#include <utility>
#include <vector>
#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_
@@ -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)
@@ -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 <algorithm>
#include <cassert>
#include <memory>
#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<spv::StorageClass>(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<Instruction*, Instruction*> 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<spv::StorageClass>(
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<Instruction*> 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 <id>
// > 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<RemapUse> 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<Instruction*> 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<uint32_t> 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<uint32_t> 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<Instruction*> 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<const analysis::Type*> 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<Instruction*> 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<Replacement> replacements;
Function::RewriteParamFn rewriter =
[&](std::unique_ptr<Instruction>&& param,
std::back_insert_iterator<Function::ParamList>& 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<Instruction>(
context(), spv::Op::OpFunctionParameter, image_type->result_id(),
context()->TakeNextId(), Instruction::OperandList{});
auto sampler_param = MakeUnique<Instruction>(
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<Instruction> opname{new Instruction{
context(),
spv::Op::OpName,
0u,
0u,
{{SPV_OPERAND_TYPE_ID, {id}},
{SPV_OPERAND_TYPE_LITERAL_STRING,
utils::MakeVector<spvtools::opt::Operand::OperandData>(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
@@ -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 <unordered_map>
#include <utility>
#include <vector>
#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<uint32_t> 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<uint32_t> 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<uint32_t> 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<uint32_t, TypeRemapInfo> 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<Instruction*, Instruction*> SplitType(
Instruction& combined_kind_type);
// The combined-image-sampler variables to be replaced.
std::vector<Instruction*> ordered_vars_;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_SPLIT_COMBINED_IMAGE_SAMPLER_PASS_H_
@@ -241,6 +241,37 @@ Handler_OpTypePointer_StorageUniformBufferBlock16(
: std::nullopt;
}
static std::optional<spv::Capability>
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<spv::Capability> 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<std::pair<spv::Op, OpcodeHandler>, 13> kOpcodeHandlers{{
constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 14> kOpcodeHandlers{{
// clang-format off
{spv::Op::OpImageRead, Handler_OpImageRead_StorageImageReadWithoutFormat},
{spv::Op::OpImageWrite, Handler_OpImageWrite_StorageImageWriteWithoutFormat},
@@ -403,6 +434,7 @@ constexpr std::array<std::pair<spv::Op, OpcodeHandler>, 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
}};
@@ -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,
+2
View File
@@ -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;
+24 -11
View File
@@ -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();
@@ -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;
@@ -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;
};
+128
View File
@@ -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<spv::Op>(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;
+3
View File
@@ -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':
+1
View File
@@ -43,6 +43,7 @@ namespace utils {
template <class T, size_t small_size>
class SmallVector {
public:
using value_type = T;
using iterator = T*;
using const_iterator = const T*;
+8 -6
View File
@@ -17,13 +17,12 @@
#include <assert.h>
#include <cstdint>
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
#include "source/util/string_utils.h"
namespace spvtools {
namespace utils {
@@ -48,8 +47,9 @@ std::pair<std::string, std::string> 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<uint32_t>* result) {
template <class VectorType = std::vector<uint32_t>>
inline void AppendToVector(const std::string& input, VectorType* result) {
static_assert(std::is_same<uint32_t, typename VectorType::value_type>::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<uint32_t> MakeVector(const std::string& input) {
std::vector<uint32_t> result;
template <class VectorType = std::vector<uint32_t>>
inline VectorType MakeVector(const std::string& input) {
static_assert(std::is_same<uint32_t, typename VectorType::value_type>::value);
VectorType result;
AppendToVector(input, &result);
return result;
}
+2 -2
View File
@@ -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: "
+209 -1
View File
@@ -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<uint32_t, bool>& 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<spv::StorageClass>(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<uint32_t>(i), cache);
}
break;
case spv::Op::OpTypeArray:
case spv::Op::OpTypeRuntimeArray:
res = UsesExplicitLayout(vstate, type_inst->GetOperandAs<uint32_t>(1),
cache);
break;
case spv::Op::OpTypePointer: {
const auto sc = type_inst->GetOperandAs<spv::StorageClass>(1);
if (!AllowsLayout(vstate, sc)) {
res = UsesExplicitLayout(
vstate, type_inst->GetOperandAs<uint32_t>(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<uint32_t, bool> 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<spv::StorageClass>(2);
auto check_id = type_id;
if (inst.opcode() == spv::Op::OpUntypedVariableKHR) {
if (inst.operands().size() > 3) {
check_id = inst.GetOperandAs<uint32_t>(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<spv::StorageClass>(1);
const auto base_type_id = inst.GetOperandAs<uint32_t>(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<uint32_t>(3))->type_id();
const auto ptr_ty = vstate.FindDef(ptr_ty_id);
const auto sc = ptr_ty->GetOperandAs<spv::StorageClass>(1);
const auto base_type_id = inst.GetOperandAs<uint32_t>(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<uint32_t>(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<spv::StorageClass>(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<uint32_t>(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<spv::StorageClass>(1);
const auto data_type_id = vstate.GetOperandTypeId(&inst, 2);
if (!AllowsLayout(vstate, sc) &&
UsesExplicitLayout(vstate, data_type_id, cache)) {
fail_id = inst.GetOperandAs<uint32_t>(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;
}
+2 -1
View File
@@ -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";
+103 -86
View File
@@ -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<uint32_t>(0));
uint32_t pointer_id = var_pointer->GetOperandAs<uint32_t>(2);
const auto isPhysicalStorageBuffer = [](const Instruction* insn) {
return insn->opcode() == spv::Op::OpTypePointer &&
insn->GetOperandAs<spv::StorageClass>(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<spv::StorageClass>(2) == spv::StorageClass::Input ||
var->GetOperandAs<spv::StorageClass>(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<uint32_t>(1)),
num_locations);
// Matrices consume locations equivalent to arrays.
if (auto error = NumConsumedLocations(
_, _.FindDef(type->GetOperandAs<uint32_t>(1)), num_locations)) {
return error;
}
*num_locations *= type->GetOperandAs<uint32_t>(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<uint32_t>(1)),
num_locations);
if (auto error = NumConsumedLocations(
_, _.FindDef(type->GetOperandAs<uint32_t>(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<uint32_t>(1)));
num_components *= type->GetOperandAs<uint32_t>(2);
break;
case spv::Op::OpTypeArray:
// Skip the array.
return NumConsumedComponents(_,
_.FindDef(type->GetOperandAs<uint32_t>(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<uint32_t>(1)));
num_components *= type->GetOperandAs<uint32_t>(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<uint32_t>(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<uint32_t>(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<spv::ExecutionModel>(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<uint32_t>(2));
if (!is_int || !is_const) array_size = 1;
auto sub_type_id = type->GetOperandAs<uint32_t>(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;
}
}
}
+39 -4
View File
@@ -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<uint32_t>(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<uint32_t>(2u));
const auto op2 = _.FindDef(inst->GetOperandAs<uint32_t>(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<spv::StorageClass>(1);
const auto sc2 = op2_type->GetOperandAs<spv::StorageClass>(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<spv::StorageClass>(1u);
if (_.addressing_model() == spv::AddressingModel::Logical) {
if (sc != spv::StorageClass::Workgroup &&
@@ -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";
}
}
}
+6 -2
View File
@@ -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
}
+1
View File
@@ -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
@@ -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"
)
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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<FactManager>(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
+14 -9
View File
@@ -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);
}
}
@@ -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) {
+128
View File
@@ -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 <cassert>
#include <string>
#include <vector>
#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 <opcode> or <result-id> at the beginning of an "
"instruction, found '1'.");
}
} // namespace
} // namespace utils
} // namespace spvtools
+2
View File
@@ -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
@@ -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<AggressiveDCEPass>(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<AggressiveDCEPass>(before, true);
}
} // namespace
} // namespace opt
} // namespace spvtools
@@ -2146,6 +2146,86 @@ OpFunctionEnd
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<CopyPropagateArrays>(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<CopyPropagateArrays>(before, true);
}
} // namespace
} // namespace opt
} // namespace spvtools
+266 -30
View File
@@ -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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
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<uint32_t>(
@@ -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<uint32_t>(
+80 -4
View File
@@ -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<InlineExhaustivePass>(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<InlineExhaustivePass>(text, true);
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// Empty modules
+32
View File
@@ -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<IRContext> 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
+118
View File
@@ -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<uint32_t> 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<uint32_t> optimized;
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized))
<< start << "\n";
tools.Disassemble(optimized.data(), optimized.size(), &default_disassembly,
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
}
{
// default passes should not benefit from additional scalar replacement
Optimizer opt(SPV_ENV_VULKAN_1_3);
opt.RegisterPerformancePasses()
.RegisterPass(CreateScalarReplacementPass(201))
.RegisterPass(CreateAggressiveDCEPass());
std::vector<uint32_t> optimized;
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized))
<< start << "\n";
tools.Disassemble(optimized.data(), optimized.size(), &test_disassembly,
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
}
EXPECT_EQ(test_disassembly, default_disassembly);
}
} // namespace
} // namespace opt
} // namespace spvtools
+49 -11
View File
@@ -35,6 +35,26 @@
namespace spvtools {
namespace opt {
inline std::ostream& operator<<(std::ostream& os,
const effcee::Result::Status ers) {
switch (ers) {
case effcee::Result::Status::Ok:
return os << "effcee::Result::Status::Ok";
case effcee::Result::Status::Fail:
return os << "effcee::Result::Status::Fail";
case effcee::Result::Status::BadOption:
return os << "effcee::Result::Status::BadOption";
case effcee::Result::Status::NoRules:
return os << "effcee::Result::Status::NoRules";
case effcee::Result::Status::BadRule:
return os << "effcee::Result::Status::BadRule";
default:
break;
}
return os << "(invalid effcee::Result::Status " << static_cast<unsigned>(ers)
<< ")";
}
// Template class for testing passes. It contains some handy utility methods for
// running passes and checking results.
//
@@ -102,23 +122,24 @@ class PassTest : public TestT {
auto status = Pass::Status::SuccessWithoutChange;
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
assembly, skip_nop, std::forward<Args>(args)...);
if (do_validation) {
spv_context spvContext = spvContextCreate(env_);
spv_diagnostic diagnostic = nullptr;
spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
spv_result_t error = spvValidateWithOptions(
spvContext, ValidatorOptions(), &binary, &diagnostic);
EXPECT_EQ(error, 0);
if (error != 0) spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
spvContextDestroy(spvContext);
}
std::string optimized_asm;
SpirvTools tools(env_);
EXPECT_TRUE(
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
<< "Disassembling failed for shader:\n"
<< assembly << std::endl;
if (do_validation) {
spv_context spvContext = spvContextCreate(env_);
spv_diagnostic diagnostic = nullptr;
spv_const_binary_t binary = {optimized_bin.data(), optimized_bin.size()};
spv_result_t error = spvValidateWithOptions(
spvContext, ValidatorOptions(), &binary, &diagnostic);
EXPECT_EQ(error, 0) << "validation failed for optimized asm:\n"
<< optimized_asm;
if (error != 0) spvDiagnosticPrint(diagnostic);
spvDiagnosticDestroy(diagnostic);
spvContextDestroy(spvContext);
}
return std::make_tuple(optimized_asm, status);
}
@@ -257,6 +278,23 @@ class PassTest : public TestT {
}
}
// Returns the disassembly of the current module. This is useful for
// debugging.
std::unique_ptr<opt::IRContext> AssembleModule(const std::string& text) {
return spvtools::BuildModule(env_, consumer_, text, assemble_options_);
}
// Returns the disassembly of the current module. This is useful for
// debugging.
std::string Disassemble(opt::Module* m) {
std::vector<uint32_t> binary;
m->ToBinary(&binary, /* skip_nop = */ false);
std::string disassembly;
SpirvTools tools(env_);
tools.Disassemble(binary, &disassembly, disassemble_options_);
return disassembly;
}
void SetAssembleOptions(uint32_t assemble_options) {
assemble_options_ = assemble_options;
}
+16
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
@@ -24,10 +25,25 @@
#include "gtest/gtest.h"
#include "include/spirv-tools/libspirv.h"
#include "include/spirv-tools/libspirv.hpp"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
inline std::ostream& operator<<(std::ostream& os, const Pass::Status ps) {
switch (ps) {
case Pass::Status::Failure:
return os << "Pass::Status::Failure";
case Pass::Status::SuccessWithChange:
return os << "Pass::Status::SuccessWithChange";
case Pass::Status::SuccessWithoutChange:
return os << "Pass::Status::SuccessWithoutChange";
default:
break;
}
return os << "(invalid Pass::Status " << static_cast<unsigned>(ps) << ")";
}
struct Message {
spv_message_level_t level;
const char* source_file;
@@ -359,6 +359,93 @@ OpFunctionEnd
SinglePassRunAndCheck<RedundancyEliminationPass>(text, text, false);
}
TEST_F(RedundancyEliminationTest, PreserveLoadYieldingImage) {
// This is more strict than needed.
// This is sufficient to ensure that the load of an image
// occurs in the same basic block as its use.
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %load_ty "load_ty"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%load_ty = OpTypeImage %float 2D 0 0 0 2 Rgba32f
%ptr_load_ty = OpTypePointer UniformConstant %load_ty
%var = OpVariable %ptr_load_ty UniformConstant
%main = OpFunction %void None %6
; CHECK: OpLoad %load_ty
; CHECK: OpLoad %load_ty
%15 = OpLabel
%16 = OpLoad %load_ty %var
%17 = OpLoad %load_ty %var
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
}
TEST_F(RedundancyEliminationTest, PreserveLoadYieldingSampler) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %load_ty "load_ty"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%load_ty = OpTypeSampler
%ptr_load_ty = OpTypePointer UniformConstant %load_ty
%var = OpVariable %ptr_load_ty UniformConstant
%main = OpFunction %void None %6
; CHECK: OpLoad %load_ty
; CHECK: OpLoad %load_ty
%15 = OpLabel
%16 = OpLoad %load_ty %var
%17 = OpLoad %load_ty %var
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
}
TEST_F(RedundancyEliminationTest, PreserveLoadYieldingSampledImage) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %load_ty "load_ty"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%i_ty = OpTypeImage %float 2D 0 0 0 2 Rgba32f
%load_ty = OpTypeSampledImage %i_ty
%ptr_load_ty = OpTypePointer UniformConstant %load_ty
%var = OpVariable %ptr_load_ty UniformConstant
%main = OpFunction %void None %6
; CHECK: OpLoad %load_ty
; CHECK: OpLoad %load_ty
%15 = OpLabel
%16 = OpLoad %load_ty %var
%17 = OpLoad %load_ty %var
OpReturn
OpFunctionEnd
)";
SinglePassRunAndMatch<RedundancyEliminationPass>(text, false);
}
} // namespace
} // namespace opt
} // namespace spvtools
} // namespace spvtools
@@ -0,0 +1,864 @@
// Copyright (c) 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <iostream>
#include <ostream>
#include "spirv-tools/optimizer.hpp"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace {
struct ResolveBindingConflictsTest : public PassTest<::testing::Test> {
virtual void SetUp() override {
SetTargetEnv(SPV_ENV_VULKAN_1_1); // allow storage buffer storage class
// without extension.
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
SPV_BINARY_TO_TEXT_OPTION_INDENT |
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
}
};
using StringList = std::vector<std::string>;
std::string EntryPointDecls(const StringList& names) {
std::ostringstream os;
for (auto& name : names) {
os << " OpEntryPoint GLCompute %" + name + " \"" + name +
"\"\n";
}
for (auto& name : names) {
os << " OpExecutionMode %" + name + " LocalSize 1 1 1\n";
}
for (auto& name : names) {
os << " OpName %" + name + " \"" + name + "\"\n";
}
return os.str();
}
std::string Preamble(const StringList& names = {"main"}) {
return R"( OpCapability Shader
OpMemoryModel Logical GLSL450
)" + EntryPointDecls(names) +
R"( OpName %voidfn "voidfn"
OpName %s_ty "s_ty"
OpName %i_ty "i_ty"
OpName %si_ty "si_ty"
OpName %p_s_ty "p_s_ty"
OpName %p_i_ty "p_i_ty"
OpName %p_si_ty "p_si_ty"
OpName %st_ty "st_ty"
OpName %pu_st_ty "pu_st_ty"
OpName %pb_st_ty "pb_st_ty"
)";
}
std::string BasicTypes() {
return R"( OpDecorate %st_ty Block
OpMemberDecorate %st_ty 0 Offset 0
%float = OpTypeFloat 32
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%s_ty = OpTypeSampler
%i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown
%si_ty = OpTypeSampledImage %i_ty
%p_i_ty = OpTypePointer UniformConstant %i_ty
%p_s_ty = OpTypePointer UniformConstant %s_ty
%p_si_ty = OpTypePointer UniformConstant %si_ty
%st_ty = OpTypeStruct %uint
%pu_st_ty = OpTypePointer Uniform %st_ty
%pb_st_ty = OpTypePointer StorageBuffer %st_ty
)";
}
std::string NoCheck() { return "; CHECK-NOT: nothing to see"; }
TEST_F(ResolveBindingConflictsTest, NoBindings_NoChange) {
const std::string kTest = Preamble() + BasicTypes() +
R"( %main = OpFunction %void None %voidfn
%100 = OpLabel
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest + NoCheck(), /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithoutChange);
EXPECT_EQ(kTest, disasm);
}
TEST_F(ResolveBindingConflictsTest, NoConflict_UnusedVars_NoChange) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 0
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 0
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 0
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 0
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 0
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 0
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 0
)" + BasicTypes() + R"(
; Unused variables
%100 = OpVariable %p_i_ty UniformConstant ; image
%101 = OpVariable %p_s_ty UniformConstant ; sampler
%102 = OpVariable %pu_st_ty Uniform ; UBO
%103 = OpVariable %pb_st_ty StorageBuffer ; SSBO
%104 = OpVariable %p_si_ty UniformConstant ; combined sampled image
%main = OpFunction %void None %voidfn
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest, NoConflict_UsedVars_NoChange) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 1
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 2
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 3
OpDecorate %110 DescriptorSet 1
OpDecorate %110 Binding 0
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 0
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 1
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %110 DescriptorSet 1
; CHECK: OpDecorate %110 Binding 0
)" + BasicTypes() + R"(
%100 = OpVariable %p_i_ty UniformConstant ; image
%101 = OpVariable %p_s_ty UniformConstant ; sampler
%102 = OpVariable %pu_st_ty Uniform ; UBO
%103 = OpVariable %pb_st_ty StorageBuffer ; SSBO
%110 = OpVariable %p_si_ty UniformConstant ; combined sampled image
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_i_ty %100
%12 = OpCopyObject %p_s_ty %101
%13 = OpCopyObject %pu_st_ty %102
%14 = OpCopyObject %pb_st_ty %103
%15 = OpCopyObject %p_si_ty %110
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
OneEntryPoint_SamplerFirstConflict_Resolves) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
; The sampler's binding number is incremented, even when listed first.
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; sampler listed first
%101 = OpVariable %p_i_ty UniformConstant
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_s_ty %100
%12 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
OneEntryPoint_SamplerSecondConflict_Resolves) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
; The sampler's binding number is incremented, even when listed second.
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 0
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 1
)" + BasicTypes() + R"(
%100 = OpVariable %p_i_ty UniformConstant
%101 = OpVariable %p_s_ty UniformConstant ; sampler listed second
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_i_ty %100
%12 = OpCopyObject %p_s_ty %101
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest, OneEntryPoint_Conflict_Ripples) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 2
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 3
; The sampler's binding number is incremented, and later
; bindings move out of the way.
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; sampler comes first
%101 = OpVariable %p_i_ty UniformConstant
%102 = OpVariable %pu_st_ty Uniform
%103 = OpVariable %pb_st_ty StorageBuffer
%104 = OpVariable %p_si_ty UniformConstant
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_s_ty %100
%12 = OpCopyObject %p_i_ty %101
%13 = OpCopyObject %pu_st_ty %102
%14 = OpCopyObject %pb_st_ty %103
%15 = OpCopyObject %p_si_ty %104
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
OneEntryPoint_Conflict_RippleStopsAtFirstHole) {
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
; Leave a hole at (0, 2)
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 3
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 4
; There was a hole at binding 2. The ripple stops there.
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; sampler comes first
%101 = OpVariable %p_i_ty UniformConstant
%102 = OpVariable %pu_st_ty Uniform
%103 = OpVariable %pb_st_ty StorageBuffer
%104 = OpVariable %p_si_ty UniformConstant
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_s_ty %100
%12 = OpCopyObject %p_i_ty %101
%13 = OpCopyObject %pu_st_ty %102
%14 = OpCopyObject %pb_st_ty %103
%15 = OpCopyObject %p_si_ty %104
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest, OneEntryPoint_MultiConflict_Resolves) {
const std::string kTest = Preamble() +
R"(
; Two conflicts: at Bindings 0, and 1
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 1
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 2
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; sampler first
%101 = OpVariable %p_i_ty UniformConstant
%102 = OpVariable %p_i_ty UniformConstant
%103 = OpVariable %p_s_ty UniformConstant ; sampler second
%104 = OpVariable %pu_st_ty Uniform
%main = OpFunction %void None %voidfn
%10 = OpLabel
%11 = OpCopyObject %p_s_ty %100
%12 = OpCopyObject %p_i_ty %101
%13 = OpCopyObject %p_i_ty %102
%14 = OpCopyObject %p_s_ty %103
%15 = OpCopyObject %pu_st_ty %104
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
OneEntryPoint_MultiConflict_ComplexCallGraph_Resolves) {
// Check that uses are seen even when used at various points in a complex call
// graph.
const std::string kTest = Preamble() +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 1
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 2
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; used in %200
%101 = OpVariable %p_i_ty UniformConstant ; used in %300, %400
%102 = OpVariable %p_i_ty UniformConstant ; used in %500
%103 = OpVariable %p_s_ty UniformConstant ; used in %400 twice
%104 = OpVariable %pu_st_ty Uniform ; used in %600
%200 = OpFunction %void None %voidfn
%201 = OpLabel
%202 = OpCopyObject %p_s_ty %100
OpReturn
OpFunctionEnd
%300 = OpFunction %void None %voidfn
%301 = OpLabel
%302 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
%400 = OpFunction %void None %voidfn
%401 = OpLabel
%402 = OpFunctionCall %void %200
%403 = OpCopyObject %p_s_ty %103
%404 = OpCopyObject %p_i_ty %101
%405 = OpCopyObject %p_s_ty %103
%406 = OpFunctionCall %void %300
OpReturn
OpFunctionEnd
%500 = OpFunction %void None %voidfn
%501 = OpLabel
%502 = OpFunctionCall %void %400
%503 = OpCopyObject %p_i_ty %102
%504 = OpFunctionCall %void %300
OpReturn
OpFunctionEnd
%600 = OpFunction %void None %voidfn
%601 = OpLabel
%602 = OpFunctionCall %void %300
%603 = OpFunctionCall %void %500
%604 = OpCopyObject %pu_st_ty %104
OpReturn
OpFunctionEnd
%main = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpFunctionCall %void %600
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
MultiEntryPoint_DuplicatConflicts_ResolvesOnlyOnce) {
// Before:
//
// Binding: 0 1
// Alpha: %100,%101
// Beta: %100,%101
//
// After:
//
// Binding: 0 1
// Alpha: %101 %100
// Beta: %101 %100
const std::string kTest = Preamble({"alpha", "beta"}) +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant
%101 = OpVariable %p_i_ty UniformConstant
%alpha = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpCopyObject %p_s_ty %100
%1002 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
%beta = OpFunction %void None %voidfn
%2000 = OpLabel
%2001 = OpCopyObject %p_s_ty %100
%2002 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
MultiEntryPoint_IndependentConflicts_Resolves) {
// Before:
//
// Binding: 0 1
// Alpha: %100,%101
// Beta: %102,%103
//
// After:
//
// Binding: 0 1
// Alpha: %101 %100
// Beta: %102 %103
const std::string kTest = Preamble({"alpha", "beta"}) +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 0
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 0
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 0
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 1
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant
%101 = OpVariable %p_i_ty UniformConstant
%102 = OpVariable %p_i_ty UniformConstant
%103 = OpVariable %p_s_ty UniformConstant
%alpha = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpCopyObject %p_s_ty %100
%1002 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
%beta = OpFunction %void None %voidfn
%2000 = OpLabel
%2001 = OpCopyObject %p_i_ty %102
%2002 = OpCopyObject %p_s_ty %103
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
MultiEntryPoint_SameVarConflictsAcrossMultiEntryPoints_Resolves) {
// A sampler variable is bumped, causing potential conflicts in other shaders.
//
// Before:
//
// Binding: 0 1 2
// Alpha: %100,%101
// Beta: %100 %102
// Gamma: %100 %103
//
// After:
//
// Binding: 0 1 2
// Alpha: %101 %100
// Beta: %100 %102
// Gamma: %100 %103
//
const std::string kTest = Preamble({"alpha", "beta", "gamma"}) +
R"(
OpDecorate %100 DescriptorSet 0 ; The sampler
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 2
; bumped once
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; pushed back from bump of %100
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; does not need to be bumped
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 2
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta, gamma
%101 = OpVariable %p_i_ty UniformConstant ; used in alpha
%102 = OpVariable %pu_st_ty Uniform ; used in beta
%103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma
%alpha = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpCopyObject %p_s_ty %100
%1002 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
%beta = OpFunction %void None %voidfn
%2000 = OpLabel
%2001 = OpCopyObject %p_s_ty %100
%2002 = OpCopyObject %pu_st_ty %102
OpReturn
OpFunctionEnd
%gamma = OpFunction %void None %voidfn
%3000 = OpLabel
%3001 = OpCopyObject %p_s_ty %100
%3002 = OpCopyObject %pb_st_ty %103
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest, MultiEntryPoint_ConflictCascade_Resolves) {
// Before:
//
// Binding: 0 1 2 3
// Alpha: %100,%101
// Beta: %100 %102
// Gamma: %102 %103
// Delta: %103 %104
//
// After:
//
// Binding: 0 1 2 3 4
// Alpha: %101 %100
// Beta: %100 %102
// Gamma: %102 %103
// Delta: %103 %104
//
const std::string kTest = Preamble({"alpha", "beta", "gamma", "delta"}) +
R"(
OpDecorate %100 DescriptorSet 0 ; The sampler
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 2
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 3
; %100 is bumped once:
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 1
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 0
; pushed back from bump of %100
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; pushed back from bump of %102
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; pushed back from bump of %103
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
)" + BasicTypes() + R"(
%100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta
%101 = OpVariable %p_i_ty UniformConstant ; used in alpha
%102 = OpVariable %pu_st_ty Uniform ; used in beta, gamma
%103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma, delta
%104 = OpVariable %p_si_ty UniformConstant ; used delta
%alpha = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpCopyObject %p_s_ty %100
%1002 = OpCopyObject %p_i_ty %101
OpReturn
OpFunctionEnd
%beta = OpFunction %void None %voidfn
%2000 = OpLabel
%2001 = OpCopyObject %p_s_ty %100
%2002 = OpCopyObject %pu_st_ty %102
OpReturn
OpFunctionEnd
%gamma = OpFunction %void None %voidfn
%3000 = OpLabel
%3001 = OpCopyObject %pu_st_ty %102
%3002 = OpCopyObject %pb_st_ty %103
OpReturn
OpFunctionEnd
%delta = OpFunction %void None %voidfn
%4000 = OpLabel
%4001 = OpCopyObject %pb_st_ty %103
%4002 = OpCopyObject %p_si_ty %104
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
TEST_F(ResolveBindingConflictsTest,
MultiEntryPoint_ConflictCascade_RevisitEntryPoint) {
// Prove that the settling algorithm knows to revisit entry points that
// already had all their own conflicts resolved.
//
// Before:
//
// Binding: 0 1 2 3 4
// Alpha: %100,%101 %103 %104 %105
// Beta: %101 %102 %103 %105
//
// After:
//
// Binding: 0 1 2 3 4 5
// Alpha: %100 %101 %103 %104 %105
// Beta: %101 %102 %103 %105
const std::string kTest = Preamble({"alpha", "beta"}) +
R"(
OpDecorate %100 DescriptorSet 0
OpDecorate %100 Binding 0
OpDecorate %101 DescriptorSet 0 ; the sampler
OpDecorate %101 Binding 0
OpDecorate %102 DescriptorSet 0
OpDecorate %102 Binding 1
OpDecorate %103 DescriptorSet 0
OpDecorate %103 Binding 2
OpDecorate %104 DescriptorSet 0
OpDecorate %104 Binding 3
OpDecorate %105 DescriptorSet 0
OpDecorate %105 Binding 4
; CHECK: OpDecorate %100 DescriptorSet 0
; CHECK: OpDecorate %100 Binding 0
; CHECK: OpDecorate %101 DescriptorSet 0
; CHECK: OpDecorate %101 Binding 1
; CHECK: OpDecorate %102 DescriptorSet 0
; CHECK: OpDecorate %102 Binding 2
; CHECK: OpDecorate %103 DescriptorSet 0
; CHECK: OpDecorate %103 Binding 3
; CHECK: OpDecorate %104 DescriptorSet 0
; CHECK: OpDecorate %104 Binding 4
; CHECK: OpDecorate %105 DescriptorSet 0
; CHECK: OpDecorate %105 Binding 5
)" + BasicTypes() + R"(
%100 = OpVariable %p_i_ty UniformConstant
%101 = OpVariable %p_s_ty UniformConstant
%102 = OpVariable %pu_st_ty Uniform
%103 = OpVariable %pb_st_ty StorageBuffer
%104 = OpVariable %p_si_ty UniformConstant
%105 = OpVariable %p_s_ty UniformConstant
%alpha = OpFunction %void None %voidfn
%1000 = OpLabel
%1001 = OpCopyObject %p_i_ty %100
%1002 = OpCopyObject %p_s_ty %101
%1003 = OpCopyObject %pb_st_ty %103
%1004 = OpCopyObject %p_si_ty %104
%1005 = OpCopyObject %p_s_ty %105
OpReturn
OpFunctionEnd
%beta = OpFunction %void None %voidfn
%2000 = OpLabel
%2001 = OpCopyObject %p_s_ty %101
%2002 = OpCopyObject %pu_st_ty %102
%2003 = OpCopyObject %pb_st_ty %103
%2004 = OpCopyObject %p_s_ty %105
OpReturn
OpFunctionEnd
)";
auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
kTest, /* do_validation= */ true);
EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}
} // namespace
} // namespace opt
} // namespace spvtools
@@ -27,7 +27,7 @@ using ScalarReplacementPassName = ::testing::Test;
TEST_F(ScalarReplacementPassName, Default) {
auto srp = ScalarReplacementPass();
EXPECT_STREQ(srp.name(), "scalar-replacement=100");
EXPECT_STREQ(srp.name(), "scalar-replacement=0");
}
TEST_F(ScalarReplacementPassName, Large) {
File diff suppressed because it is too large Load Diff
@@ -1401,6 +1401,244 @@ TEST_F(TrimCapabilitiesPassTest,
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}
TEST_F(TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemainsSimplePointer_Vulkan1_0) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability Float16
OpCapability StorageBuffer16BitAccess
; CHECK: OpCapability StorageBuffer16BitAccess
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%half = OpTypeFloat 16
%struct = OpTypeStruct %half
%ptr = OpTypePointer StorageBuffer %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_0);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}
TEST_F(TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemainsSimplePointer_Vulkan1_1) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability Float16
OpCapability StorageBuffer16BitAccess
; CHECK: OpCapability StorageBuffer16BitAccess
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%half = OpTypeFloat 16
%struct = OpTypeStruct %half
%ptr = OpTypePointer StorageBuffer %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_1);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}
TEST_F(
TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemainsSimplePointerUshortPhysicalStorage_Vulkan1_1) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability StorageBuffer16BitAccess
OpCapability PhysicalStorageBufferAddresses
; CHECK: OpCapability StorageBuffer16BitAccess
; CHECK: OpCapability PhysicalStorageBufferAddresses
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
; CHECK-NOT: OpCapability Int16
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%ushort = OpTypeInt 16 0
%struct = OpTypeStruct %ushort
%ptr = OpTypePointer PhysicalStorageBuffer %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_1);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}
TEST_F(
TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemainsSimplePointerUshortStorageBuffer_Vulkan1_1) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability StorageBuffer16BitAccess
; CHECK: OpCapability StorageBuffer16BitAccess
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
; CHECK-NOT: OpCapability Int16
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%ushort = OpTypeInt 16 0
%struct = OpTypeStruct %ushort
%ptr = OpTypePointer StorageBuffer %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_1);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}
TEST_F(
TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemainsSimplePointerUshortRecordBuffer_Vulkan1_1) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability RayTracingKHR
OpCapability StorageBuffer16BitAccess
OpExtension "SPV_KHR_ray_tracing"
; CHECK-NOT: OpCapability Int16
; CHECK: OpCapability RayTracingKHR
; CHECK: OpCapability StorageBuffer16BitAccess
; CHECK: OpExtension "SPV_KHR_ray_tracing"
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%ushort = OpTypeInt 16 0
%struct = OpTypeStruct %ushort
%ptr = OpTypePointer ShaderRecordBufferKHR %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_1);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithoutChange);
}
TEST_F(TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_TrimRecordBuffer_Vulkan1_1) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability RayTracingKHR
OpCapability StorageBuffer16BitAccess
OpExtension "SPV_KHR_ray_tracing"
; CHECK-NOT: OpCapability RayTracingKHR
; CHECK-NOT: OpCapability StorageBuffer16BitAccess
; CHECK-NOT: OpExtension "SPV_KHR_ray_tracing"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
OpDecorate %struct Block
%void = OpTypeVoid
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_VULKAN_1_1);
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}
TEST_F(TrimCapabilitiesPassTest,
StorageBuffer16BitAccess_RemovedSimplePointer) {
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/5354
static_assert(spv::Capability::StorageUniformBufferBlock16 ==
spv::Capability::StorageBuffer16BitAccess);
const std::string kTest = R"(
OpCapability Shader
OpCapability Float16
OpCapability StorageBuffer16BitAccess
OpExtension "SPV_KHR_16bit_storage"
; CHECK-NOT: OpCapability StorageBuffer16BitAccess
; CHECK-NOT: OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "main"
%void = OpTypeVoid
%half = OpTypeFloat 16
%struct = OpTypeStruct %half
%ptr = OpTypePointer Function %struct
%1 = OpTypeFunction %void
%2 = OpFunction %void None %1
%3 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto result =
SinglePassRunAndMatch<TrimCapabilitiesPass>(kTest, /* skip_nop= */ false);
EXPECT_EQ(std::get<1>(result), Pass::Status::SuccessWithChange);
}
TEST_F(TrimCapabilitiesPassTest, FragmentShaderInterlock_RemovedIfNotUsed) {
const std::string kTest = R"(
OpCapability Shader
+148 -1
View File
@@ -25,7 +25,16 @@ namespace {
using ::testing::HasSubstr;
using ::testing::MatchesRegex;
using ValueTableTest = PassTest<::testing::Test>;
struct ValueTableTest : public PassTest<::testing::Test> {
virtual void SetUp() override {
SetTargetEnv(SPV_ENV_UNIVERSAL_1_2);
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
SPV_BINARY_TO_TEXT_OPTION_INDENT |
SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
}
};
TEST_F(ValueTableTest, SameInstructionSameValue) {
const std::string text = R"(
@@ -724,9 +733,147 @@ TEST_F(ValueTableTest, RedundantSampledImageLoad) {
ValueNumberTable vtable(context.get());
Instruction* load1 = context->get_def_use_mgr()->GetDef(17);
Instruction* load2 = context->get_def_use_mgr()->GetDef(18);
// Considered the same because the underlying memory is read-only
EXPECT_EQ(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
}
TEST_F(ValueTableTest, ImageRead_ConsideredDifferent) {
// Image reads are considered different because they can be read-write storage
// image.
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %var "s"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%int = OpTypeInt 32 1
%v2int = OpTypeVector %int 2
%v2zero = OpConstantNull %v2int
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%i_ty = OpTypeImage %float 2D 0 0 0 2 Rgba32f
%ptr_s_ty = OpTypePointer UniformConstant %i_ty
%var = OpVariable %ptr_s_ty UniformConstant
%main = OpFunction %void None %6
%15 = OpLabel
%16 = OpLoad %i_ty %var
%100 = OpImageRead %v4float %16 %v2zero
%101 = OpImageRead %v4float %16 %v2zero
OpReturn
OpFunctionEnd
)";
auto context = AssembleModule(text);
ValueNumberTable vtable(context.get());
Instruction* read1 = context->get_def_use_mgr()->GetDef(100);
Instruction* read2 = context->get_def_use_mgr()->GetDef(101);
ASSERT_NE(read1, nullptr);
ASSERT_NE(read2, nullptr);
EXPECT_NE(vtable.GetValueNumber(read1), vtable.GetValueNumber(read2));
}
TEST_F(ValueTableTest, LoadSampler_ConsideredDifferent) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %var "var"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%s_ty = OpTypeSampler
%ptr_s_ty = OpTypePointer UniformConstant %s_ty
%var = OpVariable %ptr_s_ty UniformConstant
%main = OpFunction %void None %6
%15 = OpLabel
%100 = OpLoad %s_ty %var
%101 = OpLoad %s_ty %var
OpReturn
OpFunctionEnd
)";
auto context = AssembleModule(text);
ValueNumberTable vtable(context.get());
Instruction* load1 = context->get_def_use_mgr()->GetDef(100);
Instruction* load2 = context->get_def_use_mgr()->GetDef(101);
ASSERT_NE(load1, nullptr) << Disassemble(context->module());
ASSERT_NE(load2, nullptr);
EXPECT_NE(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
}
TEST_F(ValueTableTest, LoadImage_ConsideredDifferent) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %var "var"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%float = OpTypeFloat 32
%i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown
%ptr_i_ty = OpTypePointer UniformConstant %i_ty
%var = OpVariable %ptr_i_ty UniformConstant
%main = OpFunction %void None %6
%15 = OpLabel
%100 = OpLoad %i_ty %var
%101 = OpLoad %i_ty %var
OpReturn
OpFunctionEnd
)";
auto context = AssembleModule(text);
ValueNumberTable vtable(context.get());
Instruction* load1 = context->get_def_use_mgr()->GetDef(100);
Instruction* load2 = context->get_def_use_mgr()->GetDef(101);
ASSERT_NE(load1, nullptr);
ASSERT_NE(load2, nullptr);
EXPECT_NE(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
}
TEST_F(ValueTableTest, LoadSampledImage_ConsideredDifferent) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginLowerLeft
OpName %main "main"
OpName %var "var"
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%s_ty = OpTypeSampler
%float = OpTypeFloat 32
%i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown
%si_ty = OpTypeSampledImage %i_ty
%ptr_si_ty = OpTypePointer UniformConstant %si_ty
%var = OpVariable %ptr_si_ty UniformConstant
%main = OpFunction %void None %6
%15 = OpLabel
%100 = OpLoad %si_ty %var
%101 = OpLoad %si_ty %var
OpReturn
OpFunctionEnd
)";
auto context = AssembleModule(text);
ValueNumberTable vtable(context.get());
Instruction* load1 = context->get_def_use_mgr()->GetDef(100);
Instruction* load2 = context->get_def_use_mgr()->GetDef(101);
ASSERT_NE(load1, nullptr);
ASSERT_NE(load2, nullptr);
EXPECT_NE(vtable.GetValueNumber(load1), vtable.GetValueNumber(load2));
}
TEST_F(ValueTableTest, DifferentDebugLocalVariableSameValue) {
const std::string text = R"(
OpCapability Shader
+50 -3
View File
@@ -12,10 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include "gtest/gtest.h"
#include "source/util/string_utils.h"
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "source/util/small_vector.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
@@ -186,6 +189,50 @@ TEST(CardinalToOrdinal, Test) {
EXPECT_EQ("1225th", CardinalToOrdinal(1225));
}
using MakeVectorMakeStringRoundTripTest = ::testing::TestWithParam<std::string>;
TEST_P(MakeVectorMakeStringRoundTripTest, ViaStdVector) {
const std::string str = GetParam();
const auto& vec = MakeVector<std::vector<uint32_t>>(str);
// Allow for the terminating null byte that must be present.
const auto expected_vec_len = (str.size() + 4) / 4;
EXPECT_EQ(vec.size(), expected_vec_len);
const std::string back_to_string = MakeString(vec);
EXPECT_EQ(str, back_to_string);
}
TEST_P(MakeVectorMakeStringRoundTripTest, ViaSmallVector1) {
const std::string str = GetParam();
// Allow for the terminating null byte that must be present.
const auto expected_vec_len = (str.size() + 4) / 4;
const auto& vec = MakeVector<spvtools::utils::SmallVector<uint32_t, 1>>(str);
EXPECT_EQ(vec.size(), expected_vec_len);
const std::string back_to_string = MakeString(vec);
EXPECT_EQ(str, back_to_string);
}
TEST_P(MakeVectorMakeStringRoundTripTest, ViaSmallVector100) {
const std::string str = GetParam();
// Allow for the terminating null byte that must be present.
const auto expected_vec_len = (str.size() + 4) / 4;
const auto& vec =
MakeVector<spvtools::utils::SmallVector<uint32_t, 100>>(str);
EXPECT_EQ(vec.size(), expected_vec_len);
const std::string back_to_string = MakeString(vec);
EXPECT_EQ(str, back_to_string);
}
INSTANTIATE_TEST_SUITE_P(Examples, MakeVectorMakeStringRoundTripTest,
testing::ValuesIn(std::vector<std::string>{
"",
"a",
"bc",
"def",
"ghij",
"klmno",
"dustclouds disappear without a trace",
}));
} // namespace
} // namespace utils
} // namespace spvtools
+3 -3
View File
@@ -113,7 +113,7 @@ class TestValidPassFlags(expect.ValidObjectFile1_6,
'remove-duplicates',
'replace-invalid-opcode',
'ssa-rewrite',
'scalar-replacement=100',
'scalar-replacement=0',
'scalar-replacement=42',
'strength-reduction',
'strip-debug',
@@ -148,7 +148,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_6,
'eliminate-local-single-block',
'eliminate-local-single-store',
'eliminate-dead-code-aggressive',
'scalar-replacement=100',
'scalar-replacement=0',
'convert-local-access-chains',
'eliminate-local-single-block',
'eliminate-local-single-store',
@@ -162,7 +162,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_6,
'redundancy-elimination',
'combine-access-chains',
'simplify-instructions',
'scalar-replacement=100',
'scalar-replacement=0',
'convert-local-access-chains',
'eliminate-local-single-block',
'eliminate-local-single-store',
+35 -3
View File
@@ -511,6 +511,39 @@ OpControlBarrier %workgroup %device %acquire_release_subgroup
"Vulkan-supported storage class if Memory Semantics is not None"));
}
TEST_F(ValidateBarriers, OpControlBarrierVulkanAcquireRelease) {
const std::string body = R"(
OpControlBarrier %workgroup %device %acquire_release
)";
CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-04650"));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"ControlBarrier: expected Memory Semantics to include a "
"Vulkan-supported storage class if Memory Semantics is not None"));
}
TEST_F(ValidateBarriers, OpControlBarrierVulkanWorkgroupMemory) {
const std::string body = R"(
OpControlBarrier %workgroup %workgroup %workgroup_memory
)";
CompileSuccessfully(GenerateShaderCode(body), SPV_ENV_VULKAN_1_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpControlBarrier-10609"));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"ControlBarrier: Vulkan specification requires non-zero "
"Memory Semantics to have one of the following bits set: Acquire, "
"Release, AcquireRelease or SequentiallyConsistent"));
}
TEST_F(ValidateBarriers, OpControlBarrierSubgroupExecutionFragment1p1) {
const std::string body = R"(
OpControlBarrier %subgroup %subgroup %acquire_release_workgroup
@@ -687,9 +720,8 @@ OpControlBarrier %subgroup %workgroup %acquire_release_workgroup
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpControlBarrier requires one of the following "
"Execution "
"Models: TessellationControl, GLCompute, Kernel, "
"MeshNV or TaskNV"));
"Execution Models: TessellationControl, GLCompute, "
"Kernel, MeshNV or TaskNV"));
}
TEST_F(ValidateBarriers, OpMemoryBarrierSuccess) {
+12 -4
View File
@@ -427,6 +427,16 @@ TEST_F(ValidateBitwise, OpBitFieldInsertNot32Vulkan) {
HasSubstr("Expected 32-bit int type for Base operand: BitFieldInsert"));
}
TEST_F(ValidateBitwise, OpBitFieldInsertNot32Allow) {
const std::string body = R"(
%val1 = OpBitFieldInsert %u64 %u64_1 %u64_2 %s32_1 %s32_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
spvValidatorOptionsSetAllowVulkan32BitBitwise(getValidatorOptions(), true);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
TEST_F(ValidateBitwise, OpBitFieldSExtractSuccess) {
const std::string body = R"(
%val1 = OpBitFieldSExtract %u64 %u64_1 %s32_1 %s32_2
@@ -607,10 +617,8 @@ TEST_F(ValidateBitwise, OpBitCountBaseNotInt) {
%val1 = OpBitCount %u32 %f64_1
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_VULKAN_1_0);
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Base-04781"));
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
-10
View File
@@ -1422,16 +1422,6 @@ INSTANTIATE_TEST_SUITE_P(
Values(TestResult(SPV_ERROR_INVALID_DATA,
"to be used only with Fragment execution model"))));
INSTANTIATE_TEST_SUITE_P(
SampleMaskWrongStorageClass,
ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
Combine(Values("SampleMask"), Values("Fragment"), Values("Workgroup"),
Values("%u32arr2"), Values("VUID-SampleMask-SampleMask-04358"),
Values(TestResult(
SPV_ERROR_INVALID_DATA,
"Vulkan spec allows BuiltIn SampleMask to be only used for "
"variables with Input or Output storage class"))));
INSTANTIATE_TEST_SUITE_P(
SampleMaskNotArray,
ValidateVulkanCombineBuiltInExecutionModelDataTypeResult,
+370 -2
View File
@@ -7076,7 +7076,7 @@ OpFunctionEnd
CompileSuccessfully(spirv, env);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState(env));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Component-04924"));
AnyVUID("VUID-StandaloneSpirv-Component-10583"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Component decoration specified for type"));
EXPECT_THAT(getDiagnosticString(), HasSubstr("is not a scalar or vector"));
@@ -10394,7 +10394,7 @@ TEST_F(ValidateDecorations, ComponentMultipleArrays) {
OpDecorate %gl_PerVertex Block
OpDecorate %FOO Component 2
OpDecorate %FOO Location 1
OpDecorate %FOO0 Location 1
OpDecorate %FOO0 Location 4
OpDecorate %FOO0 Component 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
@@ -10697,6 +10697,374 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPre1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %block Block
%void = OpTypeVoid
%int = OpTypeInt 32 0
%block = OpTypeStruct %int
%ptr_function_block = OpTypePointer Function %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_function_block Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}
TEST_F(ValidateDecorations, InvalidLayoutBlockFunctionPost1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %block Block
%void = OpTypeVoid
%int = OpTypeInt 32 0
%block = OpTypeStruct %int
%ptr_function_block = OpTypePointer Function %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_function_block Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePre1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%block = OpTypeStruct %int
%ptr_private_block = OpTypePointer Private %block
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_private_block Private
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
TEST_F(ValidateDecorations, InvalidLayoutOffsetPrivatePost1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%block = OpTypeStruct %int
%ptr_private_block = OpTypePointer Private %block
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_private_block Private
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroupExplicitLayout) {
const std::string spirv = R"(
OpCapability Shader
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%array = OpTypeArray %int %int_4
%ptr_wg_block = OpTypePointer Workgroup %array
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_wg_block Workgroup
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
TEST_F(ValidateDecorations, InvalidLayoutArrayStrideWorkgroup) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%array = OpTypeArray %int %int_4
%ptr_wg_block = OpTypePointer Workgroup %array
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_wg_block Workgroup
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutArrayStrideUniformConstant) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %array ArrayStride 4
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%sampler = OpTypeSampler
%array = OpTypeArray %sampler %int_4
%ptr_uc_block = OpTypePointer UniformConstant %array
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_uc_block UniformConstant
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutMatrixStrideFunctionPost1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpMemberDecorate %block 0 MatrixStride 16
%void = OpTypeVoid
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%mat4x4 = OpTypeMatrix %v4float 4
%block = OpTypeStruct %mat4x4
%ptr_function_block = OpTypePointer Function %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_function_block Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutNestedMatrixStrideFunctionPost1p4) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpMemberDecorate %block 0 MatrixStride 16
%void = OpTypeVoid
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%mat4x4 = OpTypeMatrix %v4float 4
%block = OpTypeStruct %mat4x4
%block2 = OpTypeStruct %block
%int = OpTypeInt 32 0
%int_2 = OpConstant %int 2
%array = OpTypeArray %block2 %int_2
%ptr_function_array = OpTypePointer Function %array
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_function_array Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, InvalidLayoutBufferBlockWorkgroup) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %block BufferBlock
%void = OpTypeVoid
%int = OpTypeInt 32 0
%block = OpTypeStruct %int
%ptr_wg_block = OpTypePointer Workgroup %block
%void_fn = OpTypeFunction %void
%var = OpVariable %ptr_wg_block Workgroup
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid explicit layout decorations on type for operand"));
}
TEST_F(ValidateDecorations, ExplicitLayoutOnPtrPhysicalStorageBuffer) {
const std::string spirv = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_KHR_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %_ptr_PhysicalStorageBuffer_int ArrayStride 4
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 1
%_ptr_PhysicalStorageBuffer_int = OpTypePointer PhysicalStorageBuffer %int ; ArrayStride 4
%Foo = OpTypeStruct %_ptr_PhysicalStorageBuffer_int
%_ptr_Function_Foo = OpTypePointer Function %Foo
%int_0 = OpConstant %int 0
%_ptr_Function__ptr_PhysicalStorageBuffer_int = OpTypePointer Function %_ptr_PhysicalStorageBuffer_int
%ulong = OpTypeInt 64 0
%ulong_0 = OpConstant %ulong 0
%main = OpFunction %void None %void_fn
%entry = OpLabel
%obj = OpVariable %_ptr_Function_Foo Function
%obj_member = OpAccessChain %_ptr_Function__ptr_PhysicalStorageBuffer_int %obj %int_0
%nullptr = OpConvertUToPtr %_ptr_PhysicalStorageBuffer_int %ulong_0
OpStore %obj_member %nullptr
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}
TEST_F(ValidateDecorations, RuntimeArrayNotLargestOffsetInBlock) {
const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 16
OpMemberDecorate %block 1 Offset 0
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %int %array
%ptr = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr StorageBuffer
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("has a runtime array at offset 0, but other members at "
"larger offsets"));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
}
TEST_F(ValidateDecorations, RuntimeArrayNotLargestOffsetInBufferBlock) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block BufferBlock
OpMemberDecorate %block 0 Offset 16
OpMemberDecorate %block 1 Offset 0
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %int %array
%ptr = OpTypePointer Uniform %block
%var = OpVariable %ptr Uniform
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("has a runtime array at offset 0, but other members at "
"larger offsets"));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
}
} // namespace
} // namespace val
} // namespace spvtools
+2 -2
View File
@@ -5764,7 +5764,7 @@ TEST_P(ValidateIdWithMessage,
%2 = OpFunction %8 None %11
%4 = OpFunctionParameter %10
%15 = OpLabel
%16 = OpLoad %6 %3 Aligned 0
%16 = OpLoad %6 %3 Aligned 1
%17 = OpCompositeExtract %5 %16 0
%18 = OpInBoundsPtrAccessChain %13 %4 %17 %12
OpStore %18 %14 Aligned 4
@@ -5798,7 +5798,7 @@ TEST_P(ValidateIdWithMessage, OpPtrAccessChainGood) {
%2 = OpFunction %8 None %12
%4 = OpFunctionParameter %11
%17 = OpLabel
%18 = OpLoad %6 %3 Aligned 0
%18 = OpLoad %6 %3 Aligned 1
%19 = OpCompositeExtract %5 %18 0
%20 = OpBitwiseAnd %5 %19 %13
%21 = OpPtrAccessChain %15 %4 %20 %14
+224 -9
View File
@@ -1415,7 +1415,12 @@ OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08722"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Entry-point has conflicting output location "
"assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, VulkanLocationsArrayWithComponentBad) {
@@ -1428,7 +1433,7 @@ OpDecorate %11 Location 0
OpDecorate %18 Component 0
OpDecorate %18 Location 0
OpDecorate %28 Component 1
OpDecorate %28 Location 0
OpDecorate %28 Location 1
OpDecorate %36 Location 1
OpDecorate %40 Component 1
OpDecorate %40 Location 1
@@ -1543,7 +1548,12 @@ OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Entry-point has conflicting input location "
"assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, VulkanLocationArrayWithComponent2) {
@@ -1572,12 +1582,85 @@ OpMemberDecorate %struct 1 Component 2
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Entry-point has conflicting input location "
"assignment at location 0, component 0"));
}
TEST_F(ValidateInterfacesTest, VulkanLocationMatrix2x2DoubleGood) {
const std::string text = R"(
OpCapability Shader
OpCapability Float64
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %in
OpExecutionMode %main OriginUpperLeft
OpDecorate %struct Block
OpMemberDecorate %struct 0 Location 0
OpMemberDecorate %struct 1 Location 2
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%double = OpTypeFloat 64
%int = OpTypeInt 32 0
%int_2 = OpConstant %int 2
%double2 = OpTypeVector %double 2
%mat2x2_double = OpTypeMatrix %double2 2
%struct = OpTypeStruct %mat2x2_double %int
%ptr = OpTypePointer Input %struct
%in = OpVariable %ptr Input
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}
TEST_F(ValidateInterfacesTest, VulkanLocationMatrix2x2DoubleBad) {
const std::string text = R"(
OpCapability Shader
OpCapability Float64
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %in
OpExecutionMode %main OriginUpperLeft
OpDecorate %struct Block
OpMemberDecorate %struct 0 Location 0
OpMemberDecorate %struct 1 Location 1
OpMemberDecorate %struct 1 Component 3
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%double = OpTypeFloat 64
%int = OpTypeInt 32 0
%int_2 = OpConstant %int 2
%double2 = OpTypeVector %double 2
%mat2x2_double = OpTypeMatrix %double2 2
%struct = OpTypeStruct %mat2x2_double %int
%ptr = OpTypePointer Input %struct
%in = OpVariable %ptr Input
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_0);
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-OpEntryPoint-08721"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Entry-point has conflicting input location "
"assignment at location 1, component 3"));
}
TEST_F(ValidateInterfacesTest, DuplicateInterfaceVariableSuccess) {
const std::string text = R"(
OpCapability Shader
@@ -1668,7 +1751,7 @@ TEST_F(ValidateInterfacesTest, InvalidLocationTypePointer) {
HasSubstr("Invalid type to assign a location"));
}
TEST_F(ValidateInterfacesTest, ValidLocationTypePhysicalStorageBufferPointer) {
TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointer) {
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
@@ -1677,10 +1760,10 @@ OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var RestrictPointer
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypePointer PhysicalStorageBuffer %int
%ptr2 = OpTypePointer Input %ptr
%var = OpVariable %ptr2 Input
%uint = OpTypeInt 32 0
%psb_ptr = OpTypePointer PhysicalStorageBuffer %uint
%in_ptr = OpTypePointer Input %psb_ptr
%var = OpVariable %in_ptr Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
@@ -1688,7 +1771,139 @@ OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Input-09557"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Input/Output interface variable id <2> contains a "
"PhysicalStorageBuffer pointer, which is not allowed"));
}
TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerArray) {
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var RestrictPointer
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_3 = OpConstant %uint 3
%psb_ptr = OpTypePointer PhysicalStorageBuffer %uint
%array = OpTypeArray %psb_ptr %uint_3
%in_ptr = OpTypePointer Input %array
%var = OpVariable %in_ptr Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Input-09557"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Input/Output interface variable id <2> contains a "
"PhysicalStorageBuffer pointer, which is not allowed"));
}
TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerStruct) {
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var RestrictPointer
%void = OpTypeVoid
%int = OpTypeInt 32 1
OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer
%struct_0 = OpTypeStruct %int %psb_ptr
%struct_1 = OpTypeStruct %int %int
%psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1
%in_ptr = OpTypePointer Input %struct_0
%var = OpVariable %in_ptr Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Input-09557"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Input/Output interface variable id <2> contains a "
"PhysicalStorageBuffer pointer, which is not allowed"));
}
TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerArrayOfStruct) {
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var RestrictPointer
%void = OpTypeVoid
%int = OpTypeInt 32 1
%uint = OpTypeInt 32 0
%uint_3 = OpConstant %uint 3
OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer
%array_1 = OpTypeArray %psb_ptr %uint_3
%struct_0 = OpTypeStruct %int %array_1
%struct_1 = OpTypeStruct %int %int
%psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1
%array_0 = OpTypeArray %struct_0 %uint_3
%in_ptr = OpTypePointer Input %array_0
%var = OpVariable %in_ptr Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Input-09557"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Input/Output interface variable id <2> contains a "
"PhysicalStorageBuffer pointer, which is not allowed"));
}
TEST_F(ValidateInterfacesTest, PhysicalStorageBufferPointerNestedStruct) {
const std::string text = R"(
OpCapability Shader
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var RestrictPointer
%void = OpTypeVoid
%int = OpTypeInt 32 1
OpTypeForwardPointer %psb_ptr PhysicalStorageBuffer
%struct_0 = OpTypeStruct %int %psb_ptr
%struct_1 = OpTypeStruct %int %int
%psb_ptr = OpTypePointer PhysicalStorageBuffer %struct_1
%struct_2 = OpTypeStruct %int %struct_0
%in_ptr = OpTypePointer Input %struct_2
%var = OpVariable %in_ptr Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_VULKAN_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
EXPECT_THAT(getDiagnosticString(),
AnyVUID("VUID-StandaloneSpirv-Input-09557"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Input/Output interface variable id <2> contains a "
"PhysicalStorageBuffer pointer, which is not allowed"));
}
TEST_F(ValidateInterfacesTest, UntypedVariableInputMissing) {
+283 -1
View File
@@ -1818,6 +1818,68 @@ OpFunctionEnd
HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}
TEST_F(ValidateMemory, PSBStoreAlignedZero) {
const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %uint_1 Aligned 0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Memory accesses Aligned operand value 0 is not a power of two"));
}
TEST_F(ValidateMemory, PSBStoreAlignedNonPoT) {
const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %uint_1 Aligned 3
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Memory accesses Aligned operand value 3 is not a power of two."));
}
TEST_F(ValidateMemory, PSBCopyMemoryAlignedSuccess) {
const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
@@ -3883,8 +3945,11 @@ OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%ptr_int = OpTypePointer Private %int
%var = OpVariable %ptr_int Private
%ptr_float = OpTypePointer Private %float
%var2 = OpVariable %ptr_float Private
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
@@ -3897,7 +3962,7 @@ OpMemoryModel Logical GLSL450
spirv += " %bool ";
}
spirv += R"(%var %ld
spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";
@@ -3908,6 +3973,223 @@ OpFunctionEnd
HasSubstr("The types of Operand 1 and Operand 2 must match"));
}
TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameType) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameStorageClass) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypeUntypedPointerKHR StorageBuffer
%var2 = OpUntypedVariableKHR %ptr2 StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
if (operation == "OpPtrDiff") {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The types of Operand 1 and Operand 2 must match"));
} else {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
}
TEST_P(ValidatePointerComparisons, BadUntypedPointerDiffStorageClass) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypeUntypedPointerKHR Workgroup
%var2 = OpUntypedVariableKHR %ptr2 Workgroup %int
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var1 %var2
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
if (operation == "OpPtrDiff") {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The types of Operand 1 and Operand 2 must match"));
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Pointer storage classes must match"));
}
}
TEST_P(ValidatePointerComparisons, GoodMixedPointerSameStorageClass) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypePointer StorageBuffer %int
%var2 = OpVariable %ptr2 StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
if (operation == "OpPtrDiff") {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The types of Operand 1 and Operand 2 must match"));
} else {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
}
TEST_P(ValidatePointerComparisons, BadMixedPointerDiffStorageClass) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypePointer Workgroup %int
%var2 = OpVariable %ptr2 Workgroup
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var1 %var2
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
if (operation == "OpPtrDiff") {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("The types of Operand 1 and Operand 2 must match"));
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Pointer storage classes must match"));
}
}
INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons,
Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff"));
+14 -3
View File
@@ -455,9 +455,9 @@ Options (in lexicographical order):)",
instructions.)");
printf(R"(
--remove-unused-interface-variables
Removes variables referenced on the |OpEntryPoint| instruction
that are not referenced in the entry point function or any function
in its call tree. Note that this could cause the shader interface
Removes variables referenced on the |OpEntryPoint| instruction
that are not referenced in the entry point function or any function
in its call tree. Note that this could cause the shader interface
to no longer match other shader stages.)");
printf(R"(
--replace-invalid-opcode
@@ -465,6 +465,12 @@ Options (in lexicographical order):)",
but not for the current shader stage. To have an effect, all
entry points must have the same execution model.)");
printf(R"(
--resolve-binding-conflicts
Renumber bindings to avoid conflicts.
When an image and sampler share the same desriptor set and binding,
increment the binding number of the sampler. Recursively ripple
to higher-numbered bindings until all conflicts resolved resolved.)");
printf(R"(
--ssa-rewrite
Replace loads and stores to function local variables with
operations on SSA IDs.)");
@@ -501,6 +507,11 @@ Options (in lexicographical order):)",
is invalid, the optimizer may fail or generate incorrect code.
This options should be used rarely, and with caution.)");
printf(R"(
--split-combined-image-sampler
Replace combined image sampler variables and parameters into
pairs of images and samplers. New variables have the same
bindings as the original variable.)");
printf(R"(
--strength-reduction
Replaces instructions with equivalent and less expensive ones.)");
printf(R"(
+1
View File
@@ -15,6 +15,7 @@
#include "flags.h"
#include <algorithm>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <iostream>
+4
View File
@@ -68,6 +68,8 @@ Options:
be allowed by the target environment.
--allow-offset-texture-operand Allow use of the Offset texture operands where it would otherwise not
be allowed by the target environment.
--allow-vulkan-32-bit-bitwise Allow use of non-32 bit for the Base operand where it would otherwise
not be allowed by the target environment.
--before-hlsl-legalization Allows code patterns that are intended to be
fixed by spirv-opt's legalization passes.
--version Display validator version information.
@@ -165,6 +167,8 @@ int main(int argc, char** argv) {
options.SetAllowLocalSizeId(true);
} else if (0 == strcmp(cur_arg, "--allow-offset-texture-operand")) {
options.SetAllowOffsetTextureOperand(true);
} else if (0 == strcmp(cur_arg, "--allow-vulkan-32-bit-bitwise")) {
options.SetAllowVulkan32BitBitwise(true);
} else if (0 == strcmp(cur_arg, "--relax-struct-store")) {
options.SetRelaxStructStore(true);
} else if (0 == cur_arg[1]) {
+25 -5
View File
@@ -12,14 +12,34 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Script to determine if source code in Pull Request is properly formatted.
# This script determines if the source code in a Pull Request is properly formatted.
# Exits with non 0 exit code if formatting is needed.
#
# This script assumes to be invoked at the project root directory.
# Assumptions:
# - git and python3 are on the path
# - Runs from the project root diretory.
# - 'clang-format' is on the path, or env var CLANG_FORMAT points to it.
# - 'clang-format-diff.py' is in the utils directory, or env var
# points to it.CLANG_FORMAT_DIFF
BASE_BRANCH=${1:-main}
CLANG_FORMAT=${CLANG_FORMAT:-clang-format}
if [ ! -f "$CLANG_FORMAT" ]; then
echo missing clang-format: set CLANG_FORMAT or put clang-format in the PATH
exit 1
fi
# Find clang-format-diff.py from an environment variable, or use a default
CLANG_FORMAT_DIFF=${CLANG_FORMAT_DIFF:-./utils/clang-format-diff.py}
if [ ! -f "$CLANG_FORMAT_DIFF" ]; then
echo missing clang-format-diffy.py: set CLANG_FORMAT_DIFF or put it in ./utils/clang-format-diff.py
exit 1
fi
echo "Comparing "$(git rev-parse HEAD)" against $BASE_BRANCH"
echo Using $("$CLANG_FORMAT" --version)
FILES_TO_CHECK=$(git diff --name-only ${BASE_BRANCH} | grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp)$")
if [ -z "${FILES_TO_CHECK}" ]; then
@@ -27,7 +47,7 @@ if [ -z "${FILES_TO_CHECK}" ]; then
exit 0
fi
FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python ./utils/clang-format-diff.py -p1 -style=file)
FORMAT_DIFF=$(git diff -U0 ${BASE_BRANCH} -- ${FILES_TO_CHECK} | python3 "${CLANG_FORMAT_DIFF}" -p1 -style=file -binary "$CLANG_FORMAT")
if [ -z "${FORMAT_DIFF}" ]; then
echo "All source code in PR properly formatted."