Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f366ac3a35 | ||
|
|
f78770c7fb | ||
|
|
feca632486 | ||
|
|
68652347b3 | ||
|
|
585c10d9ba | ||
|
|
7e7a481320 | ||
|
|
b9a6cc5462 | ||
|
|
755912e676 |
@@ -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];
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+2
@@ -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 \
|
||||
|
||||
Vendored
+1
@@ -201,6 +201,7 @@ cc_library(
|
||||
deps = [
|
||||
":spirv_tools_internal",
|
||||
"@spirv_headers//:spirv_common_headers",
|
||||
"@spirv_headers//:spirv_c_headers",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
Vendored
+5
@@ -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",
|
||||
|
||||
Vendored
+35
@@ -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)
|
||||
|
||||
Vendored
+5
-5
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
)
|
||||
|
||||
+194
@@ -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
|
||||
+25
@@ -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
|
||||
+25
@@ -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
|
||||
+181
@@ -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
|
||||
+23
@@ -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
|
||||
+23
@@ -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
|
||||
+45
-29
@@ -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
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
+1933
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "flags.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
+4
@@ -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
@@ -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."
|
||||
|
||||
Reference in New Issue
Block a user