Updated spirv-tools.

This commit is contained in:
Бранимир Караџић
2019-05-18 12:27:37 -07:00
parent 44b5913550
commit abab48ebce
110 changed files with 8867 additions and 1641 deletions

View File

@@ -156,6 +156,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/scalar_replacement_pass.cpp \
source/opt/set_spec_constant_default_value_pass.cpp \
source/opt/simplification_pass.cpp \
source/opt/split_invalid_unreachable_pass.cpp \
source/opt/ssa_rewrite_pass.cpp \
source/opt/strength_reduction_pass.cpp \
source/opt/strip_atomic_counter_memory_pass.cpp \

View File

@@ -616,6 +616,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_invalid_unreachable_pass.cpp",
"source/opt/split_invalid_unreachable_pass.h",
"source/opt/ssa_rewrite_pass.cpp",
"source/opt/ssa_rewrite_pass.h",
"source/opt/strength_reduction_pass.cpp",

View File

@@ -1,13 +1,22 @@
Revision history for SPIRV-Tools
v2019.3-dev 2019-04-03
v2019.4-dev 2019-05-15
- Start v2019.4-dev
v2019.3 2019-05-14
- General:
- Updated Python scripts to work for both Python 2 and Python 3.
- Require Python 3 since Python 2 will out of service soon.
- Add a continuous test that does memory checks using the address sanitizer.
- Fix the build files so the SPIRV_USE_SANITIZER=address build works.
- Packaging top of tree build artifacts again.
- Added support for SPIR-V 1.4. (#2550)
- Optimizer
- Remove duplicates from list of interface IDs in OpEntryPoint instruction (#2449)
- Bindless Validation: Descriptor Initialization Check (#2419)
- Add option to validate after each pass (#2462)
- Add legalization pass to fix mismatched pointer (#2430, #2535)
- Add error messages when the input contains unknown instructions. (#2487)
- Add pass to convert from WebGPU Spir-V to Vulkan Spir-V and back. (#2495)
Fixes:
- #2412: Dead memeber elimination should not change input and output variables.
- #2405: Fix OpDot folding of half float vectors.
@@ -18,6 +27,8 @@ v2019.3-dev 2019-04-03
- #2456: Handle dead infinite loops in DCE.
- #2458: Handle variable pointer in some optimizations.
- #2452: Fix dead branch elimination to handle unreachable blocks better.
- #2528: Fix undefined bit shift in sroa.
- #2539: Change implementation of post order CFG traversal.
- Validator
- Add validation of storage classes for WebGPU (#2446)
- Add validation for ExecutionMode in WebGPU (#2443)
@@ -32,6 +43,12 @@ v2019.3-dev 2019-04-03
- Handle function decls in Structured CFG analysis (#2474)
- Validate that OpUnreacahble is not statically reachable (#2473)
- Add pass to generate needed initializers for WebGPU (#2481)
- Allow images without format for OpenCL. (#2470)
- Remove unreachable block validation (#2525)
- Reduce runtime of array layout checks (#2534)
- Add validation specific to OpExecutionModeId (#2536)
- Validate sign of int types. (#2549)
- VK_KHR_uniform_buffer_standard_layout validation (#2562)
Fixes:
- #2439: Add missing DepthGreater case to Fragment only check.
- #2168: Disallow BufferBlock on StorageBuffer variables for Vulkan.
@@ -42,6 +59,7 @@ v2019.3-dev 2019-04-03
- Allows passing options to the validator. (#2401)
- Improve reducer algorithm and other changes (#2472)
- Add Pass to remove selections (#2485)
- Add passes to simplify branches (#2507)
Fixes:
- #2478: fix loop to selection pass for loops with combined header/continue block

View File

@@ -1,10 +1,5 @@
# SPIR-V Tools
[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_release.svg)
<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_release.svg)
<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)
## Overview
The SPIR-V Tools project provides an API and commands for processing SPIR-V
@@ -24,6 +19,15 @@ SPIR-V is defined by the Khronos Group Inc.
See the [SPIR-V Registry][spirv-registry] for the SPIR-V specification,
headers, and XML registry.
## Downloads
[![Build status](https://ci.appveyor.com/api/projects/status/gpue87cesrx3pi0d/branch/master?svg=true)](https://ci.appveyor.com/project/Khronoswebmaster/spirv-tools/branch/master)
<img alt="Linux" src="kokoro/img/linux.png" width="20px" height="20px" hspace="2px"/>[![Linux Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_linux_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html)
<img alt="MacOS" src="kokoro/img/macos.png" width="20px" height="20px" hspace="2px"/>[![MacOS Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_macos_clang_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html)
<img alt="Windows" src="kokoro/img/windows.png" width="20px" height="20px" hspace="2px"/>[![Windows Build Status](https://storage.googleapis.com/spirv-tools/badges/build_status_windows_release.svg)](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html)
[More downloads](downloads.md)
## Versioning SPIRV-Tools
See [`CHANGES`](CHANGES) for a high level summary of recent changes, by version.

14
3rdparty/spirv-tools/downloads.md vendored Normal file
View File

@@ -0,0 +1,14 @@
# Downloads
Download the latest builds.
## Release
| Windows | Linux | MacOS |
| --- | --- | --- |
| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_release.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_release.html) |
| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_release.html) | |
## Debug
| Windows | Linux | MacOS |
| --- | --- | --- |
| [MSVC 2017](https://storage.googleapis.com/spirv-tools/badges/build_link_windows_vs2017_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_clang_debug.html) | [clang](https://storage.googleapis.com/spirv-tools/badges/build_link_macos_clang_debug.html) |
| | [gcc](https://storage.googleapis.com/spirv-tools/badges/build_link_linux_gcc_debug.html) | |

View File

@@ -1 +1 @@
"v2019.3-dev", "SPIRV-Tools v2019.3-dev v2019.1-159-g5fc5303e"
"v2019.4-dev", "SPIRV-Tools v2019.4-dev v2019.3-6-g47741f0"

File diff suppressed because it is too large Load Diff

View File

@@ -40,6 +40,8 @@ const char* ExtensionToString(Extension extension) {
return "SPV_INTEL_device_side_avc_motion_estimation";
case Extension::kSPV_INTEL_media_block_io:
return "SPV_INTEL_media_block_io";
case Extension::kSPV_INTEL_shader_integer_functions2:
return "SPV_INTEL_shader_integer_functions2";
case Extension::kSPV_INTEL_subgroups:
return "SPV_INTEL_subgroups";
case Extension::kSPV_KHR_16bit_storage:
@@ -105,8 +107,8 @@ const char* ExtensionToString(Extension extension) {
bool GetExtensionFromString(const char* str, Extension* extension) {
static const char* known_ext_strs[] = { "SPV_AMD_gcn_shader", "SPV_AMD_gpu_shader_half_float", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_AMD_gpu_shader_int16", "SPV_AMD_shader_ballot", "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_fragment_mask", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_texture_gather_bias_lod", "SPV_EXT_descriptor_indexing", "SPV_EXT_fragment_fully_covered", "SPV_EXT_fragment_invocation_density", "SPV_EXT_physical_storage_buffer", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_media_block_io", "SPV_INTEL_subgroups", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_device_group", "SPV_KHR_float_controls", "SPV_KHR_multiview", "SPV_KHR_no_integer_wrap_decoration", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_KHR_shader_ballot", "SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_subgroup_vote", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_compute_shader_derivatives", "SPV_NV_cooperative_matrix", "SPV_NV_fragment_shader_barycentric", "SPV_NV_geometry_shader_passthrough", "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_NV_sample_mask_override_coverage", "SPV_NV_shader_image_footprint", "SPV_NV_shader_subgroup_partitioned", "SPV_NV_shading_rate", "SPV_NV_stereo_view_rendering", "SPV_NV_viewport_array2", "SPV_VALIDATOR_ignore_type_decl_unique" };
static const Extension known_ext_ids[] = { Extension::kSPV_AMD_gcn_shader, Extension::kSPV_AMD_gpu_shader_half_float, Extension::kSPV_AMD_gpu_shader_half_float_fetch, Extension::kSPV_AMD_gpu_shader_int16, Extension::kSPV_AMD_shader_ballot, Extension::kSPV_AMD_shader_explicit_vertex_parameter, Extension::kSPV_AMD_shader_fragment_mask, Extension::kSPV_AMD_shader_image_load_store_lod, Extension::kSPV_AMD_shader_trinary_minmax, Extension::kSPV_AMD_texture_gather_bias_lod, Extension::kSPV_EXT_descriptor_indexing, Extension::kSPV_EXT_fragment_fully_covered, Extension::kSPV_EXT_fragment_invocation_density, Extension::kSPV_EXT_physical_storage_buffer, Extension::kSPV_EXT_shader_stencil_export, Extension::kSPV_EXT_shader_viewport_index_layer, Extension::kSPV_GOOGLE_decorate_string, Extension::kSPV_GOOGLE_hlsl_functionality1, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_subgroups, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_multiview, Extension::kSPV_KHR_no_integer_wrap_decoration, Extension::kSPV_KHR_post_depth_coverage, Extension::kSPV_KHR_shader_atomic_counter_ops, Extension::kSPV_KHR_shader_ballot, Extension::kSPV_KHR_shader_draw_parameters, Extension::kSPV_KHR_storage_buffer_storage_class, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_compute_shader_derivatives, Extension::kSPV_NV_cooperative_matrix, Extension::kSPV_NV_fragment_shader_barycentric, Extension::kSPV_NV_geometry_shader_passthrough, Extension::kSPV_NV_mesh_shader, Extension::kSPV_NV_ray_tracing, Extension::kSPV_NV_sample_mask_override_coverage, Extension::kSPV_NV_shader_image_footprint, Extension::kSPV_NV_shader_subgroup_partitioned, Extension::kSPV_NV_shading_rate, Extension::kSPV_NV_stereo_view_rendering, Extension::kSPV_NV_viewport_array2, Extension::kSPV_VALIDATOR_ignore_type_decl_unique };
static const char* known_ext_strs[] = { "SPV_AMD_gcn_shader", "SPV_AMD_gpu_shader_half_float", "SPV_AMD_gpu_shader_half_float_fetch", "SPV_AMD_gpu_shader_int16", "SPV_AMD_shader_ballot", "SPV_AMD_shader_explicit_vertex_parameter", "SPV_AMD_shader_fragment_mask", "SPV_AMD_shader_image_load_store_lod", "SPV_AMD_shader_trinary_minmax", "SPV_AMD_texture_gather_bias_lod", "SPV_EXT_descriptor_indexing", "SPV_EXT_fragment_fully_covered", "SPV_EXT_fragment_invocation_density", "SPV_EXT_physical_storage_buffer", "SPV_EXT_shader_stencil_export", "SPV_EXT_shader_viewport_index_layer", "SPV_GOOGLE_decorate_string", "SPV_GOOGLE_hlsl_functionality1", "SPV_INTEL_device_side_avc_motion_estimation", "SPV_INTEL_media_block_io", "SPV_INTEL_shader_integer_functions2", "SPV_INTEL_subgroups", "SPV_KHR_16bit_storage", "SPV_KHR_8bit_storage", "SPV_KHR_device_group", "SPV_KHR_float_controls", "SPV_KHR_multiview", "SPV_KHR_no_integer_wrap_decoration", "SPV_KHR_post_depth_coverage", "SPV_KHR_shader_atomic_counter_ops", "SPV_KHR_shader_ballot", "SPV_KHR_shader_draw_parameters", "SPV_KHR_storage_buffer_storage_class", "SPV_KHR_subgroup_vote", "SPV_KHR_variable_pointers", "SPV_KHR_vulkan_memory_model", "SPV_NVX_multiview_per_view_attributes", "SPV_NV_compute_shader_derivatives", "SPV_NV_cooperative_matrix", "SPV_NV_fragment_shader_barycentric", "SPV_NV_geometry_shader_passthrough", "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_NV_sample_mask_override_coverage", "SPV_NV_shader_image_footprint", "SPV_NV_shader_subgroup_partitioned", "SPV_NV_shading_rate", "SPV_NV_stereo_view_rendering", "SPV_NV_viewport_array2", "SPV_VALIDATOR_ignore_type_decl_unique" };
static const Extension known_ext_ids[] = { Extension::kSPV_AMD_gcn_shader, Extension::kSPV_AMD_gpu_shader_half_float, Extension::kSPV_AMD_gpu_shader_half_float_fetch, Extension::kSPV_AMD_gpu_shader_int16, Extension::kSPV_AMD_shader_ballot, Extension::kSPV_AMD_shader_explicit_vertex_parameter, Extension::kSPV_AMD_shader_fragment_mask, Extension::kSPV_AMD_shader_image_load_store_lod, Extension::kSPV_AMD_shader_trinary_minmax, Extension::kSPV_AMD_texture_gather_bias_lod, Extension::kSPV_EXT_descriptor_indexing, Extension::kSPV_EXT_fragment_fully_covered, Extension::kSPV_EXT_fragment_invocation_density, Extension::kSPV_EXT_physical_storage_buffer, Extension::kSPV_EXT_shader_stencil_export, Extension::kSPV_EXT_shader_viewport_index_layer, Extension::kSPV_GOOGLE_decorate_string, Extension::kSPV_GOOGLE_hlsl_functionality1, Extension::kSPV_INTEL_device_side_avc_motion_estimation, Extension::kSPV_INTEL_media_block_io, Extension::kSPV_INTEL_shader_integer_functions2, Extension::kSPV_INTEL_subgroups, Extension::kSPV_KHR_16bit_storage, Extension::kSPV_KHR_8bit_storage, Extension::kSPV_KHR_device_group, Extension::kSPV_KHR_float_controls, Extension::kSPV_KHR_multiview, Extension::kSPV_KHR_no_integer_wrap_decoration, Extension::kSPV_KHR_post_depth_coverage, Extension::kSPV_KHR_shader_atomic_counter_ops, Extension::kSPV_KHR_shader_ballot, Extension::kSPV_KHR_shader_draw_parameters, Extension::kSPV_KHR_storage_buffer_storage_class, Extension::kSPV_KHR_subgroup_vote, Extension::kSPV_KHR_variable_pointers, Extension::kSPV_KHR_vulkan_memory_model, Extension::kSPV_NVX_multiview_per_view_attributes, Extension::kSPV_NV_compute_shader_derivatives, Extension::kSPV_NV_cooperative_matrix, Extension::kSPV_NV_fragment_shader_barycentric, Extension::kSPV_NV_geometry_shader_passthrough, Extension::kSPV_NV_mesh_shader, Extension::kSPV_NV_ray_tracing, Extension::kSPV_NV_sample_mask_override_coverage, Extension::kSPV_NV_shader_image_footprint, Extension::kSPV_NV_shader_subgroup_partitioned, Extension::kSPV_NV_shading_rate, Extension::kSPV_NV_stereo_view_rendering, Extension::kSPV_NV_viewport_array2, Extension::kSPV_VALIDATOR_ignore_type_decl_unique };
const auto b = std::begin(known_ext_strs);
const auto e = std::end(known_ext_strs);
const auto found = std::equal_range(
@@ -358,6 +360,8 @@ const char* CapabilityToString(SpvCapability capability) {
return "SubgroupImageBlockIOINTEL";
case SpvCapabilitySubgroupImageMediaBlockIOINTEL:
return "SubgroupImageMediaBlockIOINTEL";
case SpvCapabilityIntegerFunctions2INTEL:
return "IntegerFunctions2INTEL";
case SpvCapabilitySubgroupAvcMotionEstimationINTEL:
return "SubgroupAvcMotionEstimationINTEL";
case SpvCapabilitySubgroupAvcMotionEstimationIntraINTEL:

View File

@@ -18,6 +18,7 @@ kSPV_GOOGLE_decorate_string,
kSPV_GOOGLE_hlsl_functionality1,
kSPV_INTEL_device_side_avc_motion_estimation,
kSPV_INTEL_media_block_io,
kSPV_INTEL_shader_integer_functions2,
kSPV_INTEL_subgroups,
kSPV_KHR_16bit_storage,
kSPV_KHR_8bit_storage,

View File

@@ -19,3 +19,4 @@
{18, "Wine", "VKD3D Shader Compiler", "Wine VKD3D Shader Compiler"},
{19, "Clay", "Clay Shader Compiler", "Clay Clay Shader Compiler"},
{20, "W3C WebGPU Group", "WHLSL Shader Translator", "W3C WebGPU Group WHLSL Shader Translator"},
{21, "Google", "Clspv", "Google Clspv"},

File diff suppressed because it is too large Load Diff

View File

@@ -427,6 +427,8 @@ typedef enum {
SPV_ENV_UNIVERSAL_1_3, // SPIR-V 1.3 latest revision, no other restrictions.
SPV_ENV_VULKAN_1_1, // Vulkan 1.1 latest revision.
SPV_ENV_WEBGPU_0, // Work in progress WebGPU 1.0.
SPV_ENV_UNIVERSAL_1_4, // SPIR-V 1.4 latest revision, no other restrictions.
SPV_ENV_VULKAN_1_1_SPIRV_1_4, // Vulkan 1.1 with SPIR-V 1.4 binary.
} spv_target_env;
// SPIR-V Validator can be parameterized with the following Universal Limits.
@@ -492,6 +494,20 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxStoreStruct(
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxLogicalPointer(
spv_validator_options options, bool val);
// Records whether or not the validator should relax the rules because it is
// expected that the optimizations will make the code legal.
//
// When relaxed, it will allow the following:
// 1) It will allow relaxed logical pointers. Setting this option will also
// set that option.
// 2) Pointers that are pass as parameters to function calls do not have to
// match the storage class of the formal parameter.
// 3) Pointers that are actaul parameters on function calls do not have to point
// to the same type pointed as the formal parameter. The types just need to
// logically match.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetBeforeHlslLegalization(
spv_validator_options options, bool val);
// Records whether the validator should use "relaxed" block layout rules.
// Relaxed layout rules are described by Vulkan extension
// VK_KHR_relaxed_block_layout, and they affect uniform blocks, storage blocks,
@@ -502,6 +518,11 @@ SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxLogicalPointer(
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetRelaxBlockLayout(
spv_validator_options options, bool val);
// Records whether the validator should use standard block layout rules for
// uniform blocks.
SPIRV_TOOLS_EXPORT void spvValidatorOptionsSetUniformBufferStandardLayout(
spv_validator_options options, bool val);
// Records whether the validator should use "scalar" block layout rules.
// Scalar layout rules are more permissive than relaxed block layout.
//

View File

@@ -88,6 +88,12 @@ class ValidatorOptions {
spvValidatorOptionsSetRelaxBlockLayout(options_, val);
}
// Enables VK_KHR_uniform_buffer_standard_layout when validating standard
// uniform layout. If true, disables scalar block layout rules.
void SetUniformBufferStandardLayout(bool val) {
spvValidatorOptionsSetUniformBufferStandardLayout(options_, val);
}
// Enables VK_EXT_scalar_block_layout when validating standard
// uniform/storage buffer/push-constant layout. If true, disables
// relaxed block layout rules.
@@ -110,6 +116,21 @@ class ValidatorOptions {
spvValidatorOptionsSetRelaxLogicalPointer(options_, val);
}
// Records whether or not the validator should relax the rules because it is
// expected that the optimizations will make the code legal.
//
// When relaxed, it will allow the following:
// 1) It will allow relaxed logical pointers. Setting this option will also
// set that option.
// 2) Pointers that are pass as parameters to function calls do not have to
// match the storage class of the formal parameter.
// 3) Pointers that are actaul parameters on function calls do not have to
// point to the same type pointed as the formal parameter. The types just
// need to logically match.
void SetBeforeHlslLegalization(bool val) {
spvValidatorOptionsSetBeforeHlslLegalization(options_, val);
}
private:
spv_validator_options options_;
};

View File

@@ -772,6 +772,10 @@ Optimizer::PassToken CreateLegalizeVectorShufflePass();
// declaration and an initial store.
Optimizer::PassToken CreateDecomposeInitializedVariablesPass();
// Create a pass to attempt to split up invalid unreachable merge-blocks and
// continue-targets to legalize for WebGPU.
Optimizer::PassToken CreateSplitInvalidUnreachablePass();
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_

View File

@@ -30,6 +30,7 @@
#include "glsl.std.450.insts.inc"
#include "opencl.std.insts.inc"
#include "spirv-tools/libspirv.h"
#include "spv-amd-gcn-shader.insts.inc"
#include "spv-amd-shader-ballot.insts.inc"
#include "spv-amd-shader-explicit-vertex-parameter.insts.inc"
@@ -80,7 +81,9 @@ spv_result_t spvExtInstTableGet(spv_ext_inst_table* pExtInstTable,
case SPV_ENV_OPENGL_4_5:
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
case SPV_ENV_WEBGPU_0:
case SPV_ENV_UNIVERSAL_1_4:
*pExtInstTable = &kTable_1_0;
return SPV_SUCCESS;
default:

View File

@@ -97,6 +97,7 @@ spv_result_t spvOpcodeTableNameLookup(spv_target_env env,
// preferable but the table requires sorting on the Opcode name, but it's
// static const initialized and matches the order of the spec.
const size_t nameLength = strlen(name);
const auto version = spvVersionForTargetEnv(env);
for (uint64_t opcodeIndex = 0; opcodeIndex < table->count; ++opcodeIndex) {
const spv_opcode_desc_t& entry = table->entries[opcodeIndex];
// We considers the current opcode as available as long as
@@ -107,7 +108,7 @@ spv_result_t spvOpcodeTableNameLookup(spv_target_env env,
// Note that the second rule assumes the extension enabling this instruction
// is indeed requested in the SPIR-V code; checking that should be
// validator's work.
if ((spvVersionForTargetEnv(env) >= entry.minVersion ||
if (((version >= entry.minVersion && version <= entry.lastVersion) ||
entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
nameLength == strlen(entry.name) &&
!strncmp(name, entry.name, nameLength)) {
@@ -130,8 +131,8 @@ spv_result_t spvOpcodeTableValueLookup(spv_target_env env,
const auto beg = table->entries;
const auto end = table->entries + table->count;
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {},
false, false, 0, nullptr, ~0u};
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {},
false, false, 0, nullptr, ~0u, ~0u};
auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
return lhs.opcode < rhs.opcode;
@@ -142,6 +143,7 @@ spv_result_t spvOpcodeTableValueLookup(spv_target_env env,
// which means they can have different minimal version requirements.
// Assumes the underlying table is already sorted ascendingly according to
// opcode value.
const auto version = spvVersionForTargetEnv(env);
for (auto it = std::lower_bound(beg, end, needle, comp);
it != end && it->opcode == opcode; ++it) {
// We considers the current opcode as available as long as
@@ -152,7 +154,7 @@ spv_result_t spvOpcodeTableValueLookup(spv_target_env env,
// Note that the second rule assumes the extension enabling this instruction
// is indeed requested in the SPIR-V code; checking that should be
// validator's work.
if (spvVersionForTargetEnv(env) >= it->minVersion ||
if ((version >= it->minVersion && version <= it->lastVersion) ||
it->numExtensions > 0u || it->numCapabilities > 0u) {
*pEntry = it;
return SPV_SUCCESS;
@@ -182,8 +184,8 @@ void spvInstructionCopy(const uint32_t* words, const SpvOp opcode,
const char* spvOpcodeString(const SpvOp opcode) {
const auto beg = kOpcodeTableEntries;
const auto end = kOpcodeTableEntries + ARRAY_SIZE(kOpcodeTableEntries);
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {},
false, false, 0, nullptr, ~0u};
spv_opcode_desc_t needle = {"", opcode, 0, nullptr, 0, {},
false, false, 0, nullptr, ~0u, ~0u};
auto comp = [](const spv_opcode_desc_t& lhs, const spv_opcode_desc_t& rhs) {
return lhs.opcode < rhs.opcode;
};

View File

@@ -16,6 +16,7 @@
#include <assert.h>
#include <string.h>
#include <algorithm>
#include "source/macro.h"
@@ -50,6 +51,7 @@ spv_result_t spvOperandTableNameLookup(spv_target_env env,
if (!table) return SPV_ERROR_INVALID_TABLE;
if (!name || !pEntry) return SPV_ERROR_INVALID_POINTER;
const auto version = spvVersionForTargetEnv(env);
for (uint64_t typeIndex = 0; typeIndex < table->count; ++typeIndex) {
const auto& group = table->types[typeIndex];
if (type != group.type) continue;
@@ -64,7 +66,7 @@ spv_result_t spvOperandTableNameLookup(spv_target_env env,
// Note that the second rule assumes the extension enabling this operand
// is indeed requested in the SPIR-V code; checking that should be
// validator's work.
if ((spvVersionForTargetEnv(env) >= entry.minVersion ||
if (((version >= entry.minVersion && version <= entry.lastVersion) ||
entry.numExtensions > 0u || entry.numCapabilities > 0u) &&
nameLength == strlen(entry.name) &&
!strncmp(entry.name, name, nameLength)) {
@@ -85,7 +87,7 @@ spv_result_t spvOperandTableValueLookup(spv_target_env env,
if (!table) return SPV_ERROR_INVALID_TABLE;
if (!pEntry) return SPV_ERROR_INVALID_POINTER;
spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u};
spv_operand_desc_t needle = {"", value, 0, nullptr, 0, nullptr, {}, ~0u, ~0u};
auto comp = [](const spv_operand_desc_t& lhs, const spv_operand_desc_t& rhs) {
return lhs.value < rhs.value;
@@ -108,6 +110,7 @@ spv_result_t spvOperandTableValueLookup(spv_target_env env,
// requirements.
// Assumes the underlying table is already sorted ascendingly according to
// opcode value.
const auto version = spvVersionForTargetEnv(env);
for (auto it = std::lower_bound(beg, end, needle, comp);
it != end && it->value == value; ++it) {
// We consider the current operand as available as long as
@@ -119,7 +122,7 @@ spv_result_t spvOperandTableValueLookup(spv_target_env env,
// Note that the second rule assumes the extension enabling this operand
// is indeed requested in the SPIR-V code; checking that should be
// validator's work.
if (spvVersionForTargetEnv(env) >= it->minVersion ||
if ((version >= it->minVersion && version <= it->lastVersion) ||
it->numExtensions > 0u || it->numCapabilities > 0u) {
*pEntry = it;
return SPV_SUCCESS;

View File

@@ -97,6 +97,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
scalar_replacement_pass.h
set_spec_constant_default_value_pass.h
simplification_pass.h
split_invalid_unreachable_pass.h
ssa_rewrite_pass.h
strength_reduction_pass.h
strip_atomic_counter_memory_pass.h
@@ -195,6 +196,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
scalar_replacement_pass.cpp
set_spec_constant_default_value_pass.cpp
simplification_pass.cpp
split_invalid_unreachable_pass.cpp
ssa_rewrite_pass.cpp
strength_reduction_pass.cpp
strip_atomic_counter_memory_pass.cpp

View File

@@ -24,6 +24,7 @@
#include "source/latest_version_glsl_std_450_header.h"
#include "source/opt/iterator.h"
#include "source/opt/reflect.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
@@ -548,7 +549,26 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
}
// Keep all entry points.
for (auto& entry : get_module()->entry_points()) {
AddToWorklist(&entry);
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// In SPIR-V 1.4 and later, entry points must list all global variables
// used. DCE can still remove non-input/output variables and update the
// interface list. Mark the entry point as live and inputs and outputs as
// live, but defer decisions all other interfaces.
live_insts_.Set(entry.unique_id());
// The actual function is live always.
AddToWorklist(
get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(1u)));
for (uint32_t i = 3; i < entry.NumInOperands(); ++i) {
auto* var = get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
auto storage_class = var->GetSingleWordInOperand(0u);
if (storage_class == SpvStorageClassInput ||
storage_class == SpvStorageClassOutput) {
AddToWorklist(var);
}
}
} else {
AddToWorklist(&entry);
}
}
// Keep workgroup size.
for (auto& anno : get_module()->annotations()) {
@@ -780,6 +800,29 @@ bool AggressiveDCEPass::ProcessGlobalValues() {
}
}
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Remove the dead interface variables from the entry point interface list.
for (auto& entry : get_module()->entry_points()) {
std::vector<Operand> new_operands;
for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
if (i < 3) {
// Execution model, function id and name are always valid.
new_operands.push_back(entry.GetInOperand(i));
} else {
auto* var =
get_def_use_mgr()->GetDef(entry.GetSingleWordInOperand(i));
if (!IsDead(var)) {
new_operands.push_back(entry.GetInOperand(i));
}
}
}
if (new_operands.size() != entry.NumInOperands()) {
entry.SetInOperands(std::move(new_operands));
get_def_use_mgr()->UpdateDefUse(&entry);
}
}
}
return modified;
}

View File

@@ -167,6 +167,10 @@ Instruction* ConstantManager::BuildInstructionAndAddToModule(
const Constant* new_const, Module::inst_iterator* pos, uint32_t type_id) {
// TODO(1841): Handle id overflow.
uint32_t new_id = context()->TakeNextId();
if (new_id == 0) {
return nullptr;
}
auto new_inst = CreateInstruction(new_id, new_const, type_id);
if (!new_inst) {
return nullptr;

View File

@@ -147,6 +147,19 @@ BasicBlock* Function::InsertBasicBlockAfter(
return nullptr;
}
BasicBlock* Function::InsertBasicBlockBefore(
std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position) {
for (auto bb_iter = begin(); bb_iter != end(); ++bb_iter) {
if (&*bb_iter == position) {
new_block->SetParent(this);
bb_iter = bb_iter.InsertBefore(std::move(new_block));
return &*bb_iter;
}
}
assert(false && "Could not find insertion point.");
return nullptr;
}
bool Function::IsRecursive() const {
IRContext* ctx = blocks_.front()->GetLabel()->context();
IRContext::ProcessFunction mark_visited = [this](Function* fp) {

View File

@@ -125,6 +125,9 @@ class Function {
BasicBlock* InsertBasicBlockAfter(std::unique_ptr<BasicBlock>&& new_block,
BasicBlock* position);
BasicBlock* InsertBasicBlockBefore(std::unique_ptr<BasicBlock>&& new_block,
BasicBlock* position);
// Return true if the function calls itself either directly or indirectly.
bool IsRecursive() const;

View File

@@ -29,6 +29,7 @@ static const int kSpvAccessChainIndex0IdInIdx = 1;
static const int kSpvTypePointerTypeIdInIdx = 1;
static const int kSpvTypeArrayLengthIdInIdx = 1;
static const int kSpvConstantValueInIdx = 0;
static const int kSpvVariableStorageClassInIdx = 0;
} // anonymous namespace
@@ -59,36 +60,42 @@ uint32_t InstBindlessCheckPass::GenDebugReadInit(uint32_t var_id,
uint32_t InstBindlessCheckPass::CloneOriginalReference(
ref_analysis* ref, InstructionBuilder* builder) {
// Clone descriptor load
Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
Instruction* new_load_inst =
builder->AddLoad(load_inst->type_id(),
load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
uid2offset_[new_load_inst->unique_id()] = uid2offset_[load_inst->unique_id()];
uint32_t new_load_id = new_load_inst->result_id();
get_decoration_mgr()->CloneDecorations(load_inst->result_id(), new_load_id);
uint32_t new_image_id = new_load_id;
// Clone Image/SampledImage with new load, if needed
if (ref->image_id != 0) {
Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
Instruction* new_image_inst = builder->AddBinaryOp(
image_inst->type_id(), SpvOpSampledImage, new_load_id,
image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
} else {
assert(image_inst->opcode() == SpvOp::SpvOpImage && "expecting OpImage");
Instruction* new_image_inst =
builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
// If original is image based, start by cloning descriptor load
uint32_t new_image_id = 0;
if (ref->desc_load_id != 0) {
Instruction* desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
Instruction* new_load_inst = builder->AddLoad(
desc_load_inst->type_id(),
desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx));
uid2offset_[new_load_inst->unique_id()] =
uid2offset_[desc_load_inst->unique_id()];
uint32_t new_load_id = new_load_inst->result_id();
get_decoration_mgr()->CloneDecorations(desc_load_inst->result_id(),
new_load_id);
new_image_id = new_load_id;
// Clone Image/SampledImage with new load, if needed
if (ref->image_id != 0) {
Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
Instruction* new_image_inst = builder->AddBinaryOp(
image_inst->type_id(), SpvOpSampledImage, new_load_id,
image_inst->GetSingleWordInOperand(kSpvSampledImageSamplerIdInIdx));
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
} else {
assert(image_inst->opcode() == SpvOp::SpvOpImage &&
"expecting OpImage");
Instruction* new_image_inst =
builder->AddUnaryOp(image_inst->type_id(), SpvOpImage, new_load_id);
uid2offset_[new_image_inst->unique_id()] =
uid2offset_[image_inst->unique_id()];
new_image_id = new_image_inst->result_id();
}
get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
}
get_decoration_mgr()->CloneDecorations(ref->image_id, new_image_id);
}
// Clone original reference using new image code
// Clone original reference
std::unique_ptr<Instruction> new_ref_inst(ref->ref_inst->Clone(context()));
uint32_t ref_result_id = ref->ref_inst->result_id();
uint32_t new_ref_id = 0;
@@ -96,7 +103,9 @@ uint32_t InstBindlessCheckPass::CloneOriginalReference(
new_ref_id = TakeNextId();
new_ref_inst->SetResultId(new_ref_id);
}
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
// Update new ref with new image if created
if (new_image_id != 0)
new_ref_inst->SetInOperand(kSpvImageSampleImageIdInIdx, {new_image_id});
// Register new reference and add to new block
Instruction* added_inst = builder->AddInstruction(std::move(new_ref_inst));
uid2offset_[added_inst->unique_id()] =
@@ -106,7 +115,7 @@ uint32_t InstBindlessCheckPass::CloneOriginalReference(
return new_ref_id;
}
uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
uint32_t InstBindlessCheckPass::GetImageId(Instruction* inst) {
switch (inst->opcode()) {
case SpvOp::SpvOpImageSampleImplicitLod:
case SpvOp::SpvOpImageSampleExplicitLod:
@@ -147,27 +156,77 @@ uint32_t InstBindlessCheckPass::GetDescriptorValueId(Instruction* inst) {
return 0;
}
Instruction* InstBindlessCheckPass::GetDescriptorTypeInst(
Instruction* var_inst) {
uint32_t var_type_id = var_inst->type_id();
Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
uint32_t desc_type_id =
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
return get_def_use_mgr()->GetDef(desc_type_id);
}
bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
ref_analysis* ref) {
ref->image_id = GetDescriptorValueId(ref_inst);
ref->ref_inst = ref_inst;
if (ref_inst->opcode() == SpvOpLoad || ref_inst->opcode() == SpvOpStore) {
ref->desc_load_id = 0;
ref->ptr_id = ref_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
if (ptr_inst->opcode() != SpvOp::SpvOpAccessChain) return false;
ref->var_id = ptr_inst->GetSingleWordInOperand(kSpvAccessChainBaseIdInIdx);
Instruction* var_inst = get_def_use_mgr()->GetDef(ref->var_id);
if (var_inst->opcode() != SpvOp::SpvOpVariable) return false;
uint32_t storage_class =
var_inst->GetSingleWordInOperand(kSpvVariableStorageClassInIdx);
switch (storage_class) {
case SpvStorageClassUniform:
case SpvStorageClassUniformConstant:
case SpvStorageClassStorageBuffer:
break;
default:
return false;
break;
}
Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
switch (desc_type_inst->opcode()) {
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
// A load through a descriptor array will have at least 3 operands. We
// do not want to instrument loads of descriptors here which are part of
// an image-based reference.
if (ptr_inst->NumInOperands() < 3) return false;
ref->index_id =
ptr_inst->GetSingleWordInOperand(kSpvAccessChainIndex0IdInIdx);
break;
default:
ref->index_id = 0;
break;
}
return true;
}
// Reference is not load or store. If not an image-based reference, return.
ref->image_id = GetImageId(ref_inst);
if (ref->image_id == 0) return false;
Instruction* image_inst = get_def_use_mgr()->GetDef(ref->image_id);
Instruction* desc_load_inst = nullptr;
if (image_inst->opcode() == SpvOp::SpvOpSampledImage) {
ref->load_id =
ref->desc_load_id =
image_inst->GetSingleWordInOperand(kSpvSampledImageImageIdInIdx);
desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
} else if (image_inst->opcode() == SpvOp::SpvOpImage) {
ref->load_id =
ref->desc_load_id =
image_inst->GetSingleWordInOperand(kSpvImageSampledImageIdInIdx);
desc_load_inst = get_def_use_mgr()->GetDef(ref->desc_load_id);
} else {
ref->load_id = ref->image_id;
ref->desc_load_id = ref->image_id;
desc_load_inst = image_inst;
ref->image_id = 0;
}
Instruction* load_inst = get_def_use_mgr()->GetDef(ref->load_id);
if (load_inst->opcode() != SpvOp::SpvOpLoad) {
if (desc_load_inst->opcode() != SpvOp::SpvOpLoad) {
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
ref->ptr_id = load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
ref->ptr_id = desc_load_inst->GetSingleWordInOperand(kSpvLoadPtrIdInIdx);
Instruction* ptr_inst = get_def_use_mgr()->GetDef(ref->ptr_id);
if (ptr_inst->opcode() == SpvOp::SpvOpVariable) {
ref->index_id = 0;
@@ -189,7 +248,6 @@ bool InstBindlessCheckPass::AnalyzeDescriptorReference(Instruction* ref_inst,
// TODO(greg-lunarg): Handle additional possibilities?
return false;
}
ref->ref_inst = ref_inst;
return true;
}
@@ -260,11 +318,7 @@ void InstBindlessCheckPass::GenBoundsCheckCode(
// If index and bound both compile-time constants and index < bound,
// return without changing
Instruction* var_inst = get_def_use_mgr()->GetDef(ref.var_id);
uint32_t var_type_id = var_inst->type_id();
Instruction* var_type_inst = get_def_use_mgr()->GetDef(var_type_id);
uint32_t desc_type_id =
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
Instruction* desc_type_inst = get_def_use_mgr()->GetDef(desc_type_id);
Instruction* desc_type_inst = GetDescriptorTypeInst(var_inst);
uint32_t length_id = 0;
if (desc_type_inst->opcode() == SpvOpTypeArray) {
length_id =

View File

@@ -118,6 +118,7 @@ class InstBindlessCheckPass : public InstrumentPass {
// AnalyzeDescriptorReference. It is necessary and sufficient for further
// analysis and regeneration of the reference.
typedef struct ref_analysis {
uint32_t desc_load_id;
uint32_t image_id;
uint32_t load_id;
uint32_t ptr_id;
@@ -131,9 +132,12 @@ class InstBindlessCheckPass : public InstrumentPass {
uint32_t CloneOriginalReference(ref_analysis* ref,
InstructionBuilder* builder);
// If |inst| references through a descriptor, (ie references into an image
// or buffer), return the id of the value it references. Else return 0.
uint32_t GetDescriptorValueId(Instruction* inst);
// If |inst| references through an image, return the id of the image it
// references through. Else return 0.
uint32_t GetImageId(Instruction* inst);
// Get descriptor type inst of variable |var_inst|.
Instruction* GetDescriptorTypeInst(Instruction* var_inst);
// Analyze descriptor reference |ref_inst| and save components into |ref|.
// Return true if |ref_inst| is a descriptor reference, false otherwise.

View File

@@ -483,7 +483,17 @@ bool Instruction::IsFoldableByFoldScalar() const {
bool Instruction::IsFloatingPointFoldingAllowed() const {
// TODO: Add the rules for kernels. For now it will be pessimistic.
if (!context_->get_feature_mgr()->HasCapability(SpvCapabilityShader)) {
// For now, do not support capabilities introduced by SPV_KHR_float_controls.
if (!context_->get_feature_mgr()->HasCapability(SpvCapabilityShader) ||
context_->get_feature_mgr()->HasCapability(SpvCapabilityDenormPreserve) ||
context_->get_feature_mgr()->HasCapability(
SpvCapabilityDenormFlushToZero) ||
context_->get_feature_mgr()->HasCapability(
SpvCapabilitySignedZeroInfNanPreserve) ||
context_->get_feature_mgr()->HasCapability(
SpvCapabilityRoundingModeRTZ) ||
context_->get_feature_mgr()->HasCapability(
SpvCapabilityRoundingModeRTE)) {
return false;
}

View File

@@ -17,6 +17,7 @@
#include "instrument_pass.h"
#include "source/cfa.h"
#include "source/spirv_constant.h"
namespace {
@@ -147,10 +148,11 @@ void InstrumentPass::GenBuiltinOutputCode(uint32_t builtin_id,
uint32_t base_offset_id,
InstructionBuilder* builder) {
// Load and store builtin
Instruction* load_inst =
builder->AddUnaryOp(GetUintId(), SpvOpLoad, builtin_id);
GenDebugOutputFieldCode(base_offset_id, builtin_off, load_inst->result_id(),
builder);
Instruction* var_inst = get_def_use_mgr()->GetDef(builtin_id);
uint32_t type_id = GetPointeeTypeId(var_inst);
Instruction* load_inst = builder->AddUnaryOp(type_id, SpvOpLoad, builtin_id);
uint32_t val_id = GenUintCastCode(load_inst->result_id(), builder);
GenDebugOutputFieldCode(base_offset_id, builtin_off, val_id, builder);
}
void InstrumentPass::GenUintNullOutputCode(uint32_t field_off,
@@ -393,6 +395,13 @@ uint32_t InstrumentPass::GetOutputBufferId() {
deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
GetOutputBufferBinding());
AddStorageBufferExt();
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Add the new buffer to all entry points.
for (auto& entry : get_module()->entry_points()) {
entry.AddOperand({SPV_OPERAND_TYPE_ID, {output_buffer_id_}});
context()->AnalyzeUses(&entry);
}
}
}
return output_buffer_id_;
}
@@ -430,6 +439,13 @@ uint32_t InstrumentPass::GetInputBufferId() {
deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
GetInputBufferBinding());
AddStorageBufferExt();
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Add the new buffer to all entry points.
for (auto& entry : get_module()->entry_points()) {
entry.AddOperand({SPV_OPERAND_TYPE_ID, {input_buffer_id_}});
context()->AnalyzeUses(&entry);
}
}
}
return input_buffer_id_;
}

View File

@@ -456,7 +456,16 @@ class IRContext {
// Return the next available SSA id and increment it. Returns 0 if the
// maximum SSA id has been reached.
inline uint32_t TakeNextId() { return module()->TakeNextIdBound(); }
inline uint32_t TakeNextId() {
uint32_t next_id = module()->TakeNextIdBound();
if (next_id == 0) {
if (consumer()) {
std::string message = "ID overflow. Try running compact-ids.";
consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
}
}
return next_id;
}
FeatureManager* get_feature_mgr() {
if (!feature_mgr_.get()) {

View File

@@ -225,6 +225,7 @@ Optimizer& Optimizer::RegisterVulkanToWebGPUPasses() {
.RegisterPass(CreateStripAtomicCounterMemoryPass())
.RegisterPass(CreateGenerateWebGPUInitializersPass())
.RegisterPass(CreateLegalizeVectorShufflePass())
.RegisterPass(CreateSplitInvalidUnreachablePass())
.RegisterPass(CreateEliminateDeadConstantPass())
.RegisterPass(CreateFlattenDecorationPass())
.RegisterPass(CreateAggressiveDCEPass())
@@ -873,4 +874,9 @@ Optimizer::PassToken CreateDecomposeInitializedVariablesPass() {
MakeUnique<opt::DecomposeInitializedVariablesPass>());
}
Optimizer::PassToken CreateSplitInvalidUnreachablePass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::SplitInvalidUnreachablePass>());
}
} // namespace spvtools

View File

@@ -53,14 +53,14 @@ uint32_t Pass::GetPointeeTypeId(const Instruction* ptrInst) const {
return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx);
}
uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
uint32_t Pass::GenerateCopy(Instruction* object_to_copy, uint32_t new_type_id,
Instruction* insertion_position) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
uint32_t original_type_id = object_inst->type_id();
uint32_t original_type_id = object_to_copy->type_id();
if (original_type_id == new_type_id) {
return object_inst->result_id();
return object_to_copy->result_id();
}
InstructionBuilder ir_builder(
@@ -86,7 +86,7 @@ uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
uint32_t array_length = length_const->AsIntConstant()->GetU32();
for (uint32_t i = 0; i < array_length; i++) {
Instruction* extract = ir_builder.AddCompositeExtract(
original_element_type_id, object_inst->result_id(), {i});
original_element_type_id, object_to_copy->result_id(), {i});
element_ids.push_back(
GenerateCopy(extract, new_element_type_id, insertion_position));
}
@@ -104,7 +104,7 @@ uint32_t Pass::GenerateCopy(Instruction* object_inst, uint32_t new_type_id,
std::vector<uint32_t> element_ids;
for (uint32_t i = 0; i < original_types.size(); i++) {
Instruction* extract = ir_builder.AddCompositeExtract(
type_mgr->GetId(original_types[i]), object_inst->result_id(), {i});
type_mgr->GetId(original_types[i]), object_to_copy->result_id(), {i});
element_ids.push_back(GenerateCopy(extract, type_mgr->GetId(new_types[i]),
insertion_position));
}

View File

@@ -65,6 +65,7 @@
#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_invalid_unreachable_pass.h"
#include "source/opt/ssa_rewrite_pass.h"
#include "source/opt/strength_reduction_pass.h"
#include "source/opt/strip_atomic_counter_memory_pass.h"

View File

@@ -19,6 +19,7 @@
#include <vector>
#include "source/opt/ir_context.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
@@ -38,6 +39,7 @@ Pass::Status PrivateToLocalPass::Process() {
return Status::SuccessWithoutChange;
std::vector<std::pair<Instruction*, Function*>> variables_to_move;
std::unordered_set<uint32_t> localized_variables;
for (auto& inst : context()->types_values()) {
if (inst.opcode() != SpvOpVariable) {
continue;
@@ -57,6 +59,27 @@ Pass::Status PrivateToLocalPass::Process() {
modified = !variables_to_move.empty();
for (auto p : variables_to_move) {
MoveVariable(p.first, p.second);
localized_variables.insert(p.first->result_id());
}
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// In SPIR-V 1.4 and later entry points must list private storage class
// variables that are statically used by the entry point. Go through the
// entry points and remove any references to variables that were localized.
for (auto& entry : get_module()->entry_points()) {
std::vector<Operand> new_operands;
for (uint32_t i = 0; i < entry.NumInOperands(); ++i) {
// Execution model, function id and name are always kept.
if (i < 3 ||
!localized_variables.count(entry.GetSingleWordInOperand(i))) {
new_operands.push_back(entry.GetInOperand(i));
}
}
if (new_operands.size() != entry.NumInOperands()) {
entry.SetInOperands(std::move(new_operands));
context()->AnalyzeUses(&entry);
}
}
}
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
@@ -165,6 +188,7 @@ void PrivateToLocalPass::UpdateUse(Instruction* inst) {
UpdateUses(inst->result_id());
break;
case SpvOpName:
case SpvOpEntryPoint: // entry points will be updated separately.
break;
default:
assert(spvOpcodeIsDecoration(inst->opcode()) &&

View File

@@ -72,7 +72,9 @@ Pass::Status ScalarReplacementPass::ProcessFunction(Function* function) {
bool ScalarReplacementPass::ReplaceVariable(
Instruction* inst, std::queue<Instruction*>* worklist) {
std::vector<Instruction*> replacements;
CreateReplacementVariables(inst, &replacements);
if (!CreateReplacementVariables(inst, &replacements)) {
return false;
}
std::vector<Instruction*> dead;
dead.push_back(inst);
@@ -257,7 +259,7 @@ bool ScalarReplacementPass::ReplaceAccessChain(
return true;
}
void ScalarReplacementPass::CreateReplacementVariables(
bool ScalarReplacementPass::CreateReplacementVariables(
Instruction* inst, std::vector<Instruction*>* replacements) {
Instruction* type = GetStorageType(inst);
@@ -302,6 +304,8 @@ void ScalarReplacementPass::CreateReplacementVariables(
}
TransferAnnotations(inst, replacements);
return std::find(replacements->begin(), replacements->end(), nullptr) ==
replacements->end();
}
void ScalarReplacementPass::TransferAnnotations(
@@ -801,7 +805,9 @@ Instruction* ScalarReplacementPass::CreateNullConstant(uint32_t type_id) {
const analysis::Constant* null_const = const_mgr->GetConstant(type, {});
Instruction* null_inst =
const_mgr->GetDefiningInstruction(null_const, type_id);
context()->UpdateDefUse(null_inst);
if (null_inst != nullptr) {
context()->UpdateDefUse(null_inst);
}
return null_inst;
}

View File

@@ -117,8 +117,8 @@ class ScalarReplacementPass : public Pass {
// for element of the composite type. Uses of |inst| are updated as
// appropriate. If the replacement variables are themselves scalarizable, they
// get added to |worklist| for further processing. If any replacement
// variable ends up with no uses it is erased. Returns false if any
// subsequent access chain is out of bounds.
// variable ends up with no uses it is erased. Returns false if the variable
// could not be replaced.
bool ReplaceVariable(Instruction* inst, std::queue<Instruction*>* worklist);
// Returns the underlying storage type for |inst|.
@@ -145,13 +145,14 @@ class ScalarReplacementPass : public Pass {
std::vector<Instruction*>* replacements);
// Populates |replacements| with a new OpVariable for each element of |inst|.
// Returns true if the replacement variables were successfully created.
//
// |inst| must be an OpVariable of a composite type. New variables are
// initialized the same as the corresponding index in |inst|. |replacements|
// will contain a variable for each element of the composite with matching
// indexes (i.e. the 0'th element of |inst| is the 0'th entry of
// |replacements|).
void CreateReplacementVariables(Instruction* inst,
bool CreateReplacementVariables(Instruction* inst,
std::vector<Instruction*>* replacements);
// Returns the value of an OpConstant of integer type.
@@ -217,6 +218,7 @@ class ScalarReplacementPass : public Pass {
// Returns an instruction defining a null constant with type |type_id|. If
// one already exists, it is returned. Otherwise a new one is created.
// Returns |nullptr| if the new constant could not be created.
Instruction* CreateNullConstant(uint32_t type_id);
// Maps storage type to a pointer type enclosing that type.

View File

@@ -0,0 +1,95 @@
// Copyright (c) 2019 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_invalid_unreachable_pass.h"
#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
Pass::Status SplitInvalidUnreachablePass::Process() {
bool changed = false;
std::unordered_set<uint32_t> entry_points;
for (auto entry_point : context()->module()->entry_points()) {
entry_points.insert(entry_point.GetSingleWordOperand(1));
}
for (auto func = context()->module()->begin();
func != context()->module()->end(); ++func) {
if (entry_points.find(func->result_id()) == entry_points.end()) continue;
std::unordered_set<uint32_t> continue_targets;
std::unordered_set<uint32_t> merge_blocks;
std::unordered_set<BasicBlock*> unreachable_blocks;
for (auto block = func->begin(); block != func->end(); ++block) {
unreachable_blocks.insert(&*block);
uint32_t continue_target = block->ContinueBlockIdIfAny();
if (continue_target != 0) continue_targets.insert(continue_target);
uint32_t merge_block = block->MergeBlockIdIfAny();
if (merge_block != 0) merge_blocks.insert(merge_block);
}
cfg()->ForEachBlockInPostOrder(
func->entry().get(), [&unreachable_blocks](BasicBlock* inner_block) {
unreachable_blocks.erase(inner_block);
});
for (auto unreachable : unreachable_blocks) {
uint32_t block_id = unreachable->id();
if (continue_targets.find(block_id) == continue_targets.end() ||
merge_blocks.find(block_id) == merge_blocks.end()) {
continue;
}
std::vector<std::tuple<Instruction*, uint32_t>> usages;
context()->get_def_use_mgr()->ForEachUse(
unreachable->GetLabelInst(),
[&usages](Instruction* use, uint32_t idx) {
if ((use->opcode() == SpvOpLoopMerge && idx == 0) ||
use->opcode() == SpvOpSelectionMerge) {
usages.push_back(std::make_pair(use, idx));
}
});
for (auto usage : usages) {
Instruction* use;
uint32_t idx;
std::tie(use, idx) = usage;
uint32_t new_id = context()->TakeNextId();
std::unique_ptr<Instruction> new_label(
new Instruction(context(), SpvOpLabel, 0, new_id, {}));
get_def_use_mgr()->AnalyzeInstDefUse(new_label.get());
std::unique_ptr<BasicBlock> new_block(
new BasicBlock(std::move(new_label)));
auto* block_ptr = new_block.get();
InstructionBuilder builder(context(), new_block.get(),
IRContext::kAnalysisDefUse |
IRContext::kAnalysisInstrToBlockMapping);
builder.AddUnreachable();
cfg()->RegisterBlock(block_ptr);
(&*func)->InsertBasicBlockBefore(std::move(new_block), unreachable);
use->SetInOperand(0, {new_id});
get_def_use_mgr()->UpdateDefUse(use);
cfg()->AddEdges(block_ptr);
changed = true;
}
}
}
return changed ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,51 @@
// Copyright (c) 2019 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 SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_
#define SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_
#include "source/opt/ir_context.h"
#include "source/opt/module.h"
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// Attempts to legalize for WebGPU by splitting up invalid unreachable blocks.
// Specifically, looking for cases of unreachable merge-blocks and
// continue-targets that are used more then once, which is illegal in WebGPU.
class SplitInvalidUnreachablePass : public Pass {
public:
const char* name() const override { return "split-invalid-unreachable"; }
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return 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::kAnalysisTypes |
IRContext::kAnalysisDefUse | IRContext::kAnalysisConstants;
}
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_SPLIT_INVALID_UNREACHABLE_PASS_H_

View File

@@ -18,6 +18,7 @@
#include "source/opt/ir_builder.h"
#include "source/opt/ir_context.h"
#include "source/spirv_constant.h"
#include "source/util/make_unique.h"
namespace spvtools {
@@ -70,6 +71,7 @@ void UpgradeMemoryModel::UpgradeInstructions() {
// parameters are implicitly coherent in GLSL450.
// Upgrade modf and frexp first since they generate new stores.
// In SPIR-V 1.4 or later, normalize OpCopyMemory* access operands.
for (auto& func : *get_module()) {
func.ForEachInst([this](Instruction* inst) {
if (inst->opcode() == SpvOpExtInst) {
@@ -82,6 +84,29 @@ void UpgradeMemoryModel::UpgradeInstructions() {
UpgradeExtInst(inst);
}
}
} else if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
if (inst->opcode() == SpvOpCopyMemory ||
inst->opcode() == SpvOpCopyMemorySized) {
uint32_t start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
if (inst->NumInOperands() > start_operand) {
auto num_access_words = MemoryAccessNumWords(
inst->GetSingleWordInOperand(start_operand));
if ((num_access_words + start_operand) == inst->NumInOperands()) {
// There is a single memory access operand. Duplicate it to have a
// separate operand for both source and target.
for (uint32_t i = 0; i < num_access_words; ++i) {
auto operand = inst->GetInOperand(start_operand + i);
inst->AddOperand(std::move(operand));
}
}
} else {
// Add two memory access operands.
inst->AddOperand(
{SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
inst->AddOperand(
{SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessMaskNone}});
}
}
}
});
}
@@ -93,6 +118,7 @@ void UpgradeMemoryModel::UpgradeInstructions() {
bool src_volatile = false;
bool dst_coherent = false;
bool dst_volatile = false;
uint32_t start_operand = 0u;
SpvScope scope = SpvScopeQueueFamilyKHR;
SpvScope src_scope = SpvScopeQueueFamilyKHR;
SpvScope dst_scope = SpvScopeQueueFamilyKHR;
@@ -129,16 +155,23 @@ void UpgradeMemoryModel::UpgradeInstructions() {
kMemory);
break;
case SpvOpCopyMemory:
UpgradeFlags(inst, 2u, dst_coherent, dst_volatile, kAvailability,
kMemory);
UpgradeFlags(inst, 2u, src_coherent, src_volatile, kVisibility,
kMemory);
break;
case SpvOpCopyMemorySized:
UpgradeFlags(inst, 3u, dst_coherent, dst_volatile, kAvailability,
kMemory);
UpgradeFlags(inst, 3u, src_coherent, src_volatile, kVisibility,
kMemory);
start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// There are guaranteed to be two memory access operands at this
// point so treat source and target separately.
uint32_t num_access_words = MemoryAccessNumWords(
inst->GetSingleWordInOperand(start_operand));
UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
kAvailability, kMemory);
UpgradeFlags(inst, start_operand + num_access_words, src_coherent,
src_volatile, kVisibility, kMemory);
} else {
UpgradeFlags(inst, start_operand, dst_coherent, dst_volatile,
kAvailability, kMemory);
UpgradeFlags(inst, start_operand, src_coherent, src_volatile,
kVisibility, kMemory);
}
break;
case SpvOpImageRead:
case SpvOpImageSparseRead:
@@ -158,16 +191,49 @@ void UpgradeMemoryModel::UpgradeInstructions() {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(scope)}});
}
// According to SPV_KHR_vulkan_memory_model, if both available and
// visible flags are used the first scope operand is for availability
// (writes) and the second is for visibility (reads).
if (dst_coherent) {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
}
if (src_coherent) {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
if (get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// There are two memory access operands. The first is for the target and
// the second is for the source.
if (dst_coherent || src_coherent) {
start_operand = inst->opcode() == SpvOpCopyMemory ? 2u : 3u;
std::vector<Operand> new_operands;
uint32_t num_access_words =
MemoryAccessNumWords(inst->GetSingleWordInOperand(start_operand));
// The flags were already updated so subtract if we're adding a
// scope.
if (dst_coherent) --num_access_words;
for (uint32_t i = 0; i < start_operand + num_access_words; ++i) {
new_operands.push_back(inst->GetInOperand(i));
}
// Add the target scope if necessary.
if (dst_coherent) {
new_operands.push_back(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
}
// Copy the remaining current operands.
for (uint32_t i = start_operand + num_access_words;
i < inst->NumInOperands(); ++i) {
new_operands.push_back(inst->GetInOperand(i));
}
// Add the source scope if necessary.
if (src_coherent) {
new_operands.push_back(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
}
inst->SetInOperands(std::move(new_operands));
}
} else {
// According to SPV_KHR_vulkan_memory_model, if both available and
// visible flags are used the first scope operand is for availability
// (writes) and the second is for visibility (reads).
if (dst_coherent) {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(dst_scope)}});
}
if (src_coherent) {
inst->AddOperand(
{SPV_OPERAND_TYPE_SCOPE_ID, {GetScopeConstant(src_scope)}});
}
}
});
}
@@ -636,5 +702,13 @@ void UpgradeMemoryModel::UpgradeExtInst(Instruction* ext_inst) {
builder.AddStore(ptr_id, extract_1->result_id());
}
uint32_t UpgradeMemoryModel::MemoryAccessNumWords(uint32_t mask) {
uint32_t result = 1;
if (mask & SpvMemoryAccessAlignedMask) ++result;
if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
return result;
}
} // namespace opt
} // namespace spvtools

View File

@@ -15,11 +15,11 @@
#ifndef LIBSPIRV_OPT_UPGRADE_MEMORY_MODEL_H_
#define LIBSPIRV_OPT_UPGRADE_MEMORY_MODEL_H_
#include "pass.h"
#include <functional>
#include <tuple>
#include "pass.h"
namespace spvtools {
namespace opt {
@@ -123,6 +123,10 @@ class UpgradeMemoryModel : public Pass {
// facilitate adding memory model flags.
void UpgradeExtInst(Instruction* modf);
// Returns the number of words taken up by a memory access argument and its
// implied operands.
uint32_t MemoryAccessNumWords(uint32_t mask);
// Caches the result of TraceInstruction. For a given result id and set of
// indices, stores whether that combination is coherent and/or volatile.
std::unordered_map<std::pair<uint32_t, std::vector<uint32_t>>,

View File

@@ -15,6 +15,7 @@
#include "source/spirv_target_env.h"
#include <cstring>
#include <string>
#include "source/spirv_constant.h"
#include "spirv-tools/libspirv.h"
@@ -61,6 +62,10 @@ const char* spvTargetEnvDescription(spv_target_env env) {
return "SPIR-V 1.3 (under Vulkan 1.1 semantics)";
case SPV_ENV_WEBGPU_0:
return "SPIR-V 1.3 (under WIP WebGPU semantics)";
case SPV_ENV_UNIVERSAL_1_4:
return "SPIR-V 1.4";
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
return "SPIR-V 1.4 (under Vulkan 1.1 semantics)";
}
return "";
}
@@ -91,6 +96,9 @@ uint32_t spvVersionForTargetEnv(spv_target_env env) {
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_WEBGPU_0:
return SPV_SPIRV_VERSION_WORD(1, 3);
case SPV_ENV_UNIVERSAL_1_4:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
return SPV_SPIRV_VERSION_WORD(1, 4);
}
return SPV_SPIRV_VERSION_WORD(0, 0);
}
@@ -99,7 +107,10 @@ bool spvParseTargetEnv(const char* s, spv_target_env* env) {
auto match = [s](const char* b) {
return s && (0 == strncmp(s, b, strlen(b)));
};
if (match("vulkan1.0")) {
if (match("vulkan1.1spv1.4")) {
if (env) *env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
return true;
} else if (match("vulkan1.0")) {
if (env) *env = SPV_ENV_VULKAN_1_0;
return true;
} else if (match("vulkan1.1")) {
@@ -117,6 +128,9 @@ bool spvParseTargetEnv(const char* s, spv_target_env* env) {
} else if (match("spv1.3")) {
if (env) *env = SPV_ENV_UNIVERSAL_1_3;
return true;
} else if (match("spv1.4")) {
if (env) *env = SPV_ENV_UNIVERSAL_1_4;
return true;
} else if (match("opencl1.2embedded")) {
if (env) *env = SPV_ENV_OPENCL_EMBEDDED_1_2;
return true;
@@ -185,9 +199,11 @@ bool spvIsVulkanEnv(spv_target_env env) {
case SPV_ENV_OPENCL_EMBEDDED_2_2:
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_WEBGPU_0:
case SPV_ENV_UNIVERSAL_1_4:
return false;
case SPV_ENV_VULKAN_1_0:
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
return true;
}
return false;
@@ -207,6 +223,8 @@ bool spvIsOpenCLEnv(spv_target_env env) {
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_WEBGPU_0:
case SPV_ENV_UNIVERSAL_1_4:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
return false;
case SPV_ENV_OPENCL_1_2:
case SPV_ENV_OPENCL_EMBEDDED_1_2:
@@ -242,6 +260,8 @@ bool spvIsWebGPUEnv(spv_target_env env) {
case SPV_ENV_OPENCL_EMBEDDED_2_2:
case SPV_ENV_OPENCL_2_1:
case SPV_ENV_OPENCL_2_2:
case SPV_ENV_UNIVERSAL_1_4:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
return false;
case SPV_ENV_WEBGPU_0:
return true;
@@ -273,7 +293,8 @@ std::string spvLogStringForEnv(spv_target_env env) {
return "OpenGL";
}
case SPV_ENV_VULKAN_1_0:
case SPV_ENV_VULKAN_1_1: {
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4: {
return "Vulkan";
}
case SPV_ENV_WEBGPU_0: {
@@ -282,7 +303,8 @@ std::string spvLogStringForEnv(spv_target_env env) {
case SPV_ENV_UNIVERSAL_1_0:
case SPV_ENV_UNIVERSAL_1_1:
case SPV_ENV_UNIVERSAL_1_2:
case SPV_ENV_UNIVERSAL_1_3: {
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_UNIVERSAL_1_4: {
return "Universal";
}
}

View File

@@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/spirv_validator_options.h"
#include <cassert>
#include <cstring>
#include "source/spirv_validator_options.h"
bool spvParseUniversalLimitsOptions(const char* s, spv_validator_limit* type) {
auto match = [s](const char* b) {
return s && (0 == strncmp(s, b, strlen(b)));
@@ -90,11 +90,22 @@ void spvValidatorOptionsSetRelaxLogicalPointer(spv_validator_options options,
options->relax_logical_pointer = val;
}
void spvValidatorOptionsSetBeforeHlslLegalization(spv_validator_options options,
bool val) {
options->before_hlsl_legalization = val;
options->relax_logical_pointer = val;
}
void spvValidatorOptionsSetRelaxBlockLayout(spv_validator_options options,
bool val) {
options->relax_block_layout = val;
}
void spvValidatorOptionsSetUniformBufferStandardLayout(
spv_validator_options options, bool val) {
options->uniform_buffer_standard_layout = val;
}
void spvValidatorOptionsSetScalarBlockLayout(spv_validator_options options,
bool val) {
options->scalar_block_layout = val;

View File

@@ -43,15 +43,19 @@ struct spv_validator_options_t {
relax_struct_store(false),
relax_logical_pointer(false),
relax_block_layout(false),
uniform_buffer_standard_layout(false),
scalar_block_layout(false),
skip_block_layout(false) {}
skip_block_layout(false),
before_hlsl_legalization(false) {}
validator_universal_limits_t universal_limits_;
bool relax_struct_store;
bool relax_logical_pointer;
bool relax_block_layout;
bool uniform_buffer_standard_layout;
bool scalar_block_layout;
bool skip_block_layout;
bool before_hlsl_legalization;
};
#endif // SOURCE_SPIRV_VALIDATOR_OPTIONS_H_

View File

@@ -37,7 +37,9 @@ spv_context spvContextCreate(spv_target_env env) {
case SPV_ENV_UNIVERSAL_1_2:
case SPV_ENV_UNIVERSAL_1_3:
case SPV_ENV_VULKAN_1_1:
case SPV_ENV_VULKAN_1_1_SPIRV_1_4:
case SPV_ENV_WEBGPU_0:
case SPV_ENV_UNIVERSAL_1_4:
break;
default:
return nullptr;

View File

@@ -15,9 +15,8 @@
#ifndef SOURCE_TABLE_H_
#define SOURCE_TABLE_H_
#include "source/latest_version_spirv_header.h"
#include "source/extensions.h"
#include "source/latest_version_spirv_header.h"
#include "spirv-tools/libspirv.hpp"
typedef struct spv_opcode_desc_t {
@@ -42,6 +41,7 @@ typedef struct spv_opcode_desc_t {
// extensions. ~0u means reserved for future use. ~0u and non-empty extension
// lists means only available in extensions.
const uint32_t minVersion;
const uint32_t lastVersion;
} spv_opcode_desc_t;
typedef struct spv_operand_desc_t {
@@ -60,6 +60,7 @@ typedef struct spv_operand_desc_t {
// extensions. ~0u means reserved for future use. ~0u and non-empty extension
// lists means only available in extensions.
const uint32_t minVersion;
const uint32_t lastVersion;
} spv_operand_desc_t;
typedef struct spv_operand_desc_group_t {

View File

@@ -19,6 +19,7 @@
#include <unordered_set>
#include "source/val/function.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
@@ -105,7 +106,8 @@ Construct::ConstructBlockSet Construct::blocks(Function* function) const {
// A selection construct nested directly within the loop construct is also
// at the same depth. It is valid, however, to branch directly to the
// continue target from within the selection construct.
if (block_depth == header_depth && type() == ConstructType::kSelection &&
if (block != header && block_depth == header_depth &&
type() == ConstructType::kSelection &&
block->is_type(kBlockTypeContinue)) {
// Continued to outer construct.
continue;
@@ -126,5 +128,72 @@ Construct::ConstructBlockSet Construct::blocks(Function* function) const {
return construct_blocks;
}
bool Construct::IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const {
// Structured Exits:
// - Selection:
// - branch to its merge
// - branch to nearest enclosing loop merge or continue
// - Loop:
// - branch to its merge
// - branch to its continue
// - Continue:
// - branch to loop header
// - branch to loop merge
//
// Note: we will never see a case construct here.
assert(type() != ConstructType::kCase);
if (type() == ConstructType::kLoop) {
auto header = entry_block();
auto terminator = header->terminator();
auto index = terminator - &_.ordered_instructions()[0];
auto merge_inst = &_.ordered_instructions()[index - 1];
auto merge_block_id = merge_inst->GetOperandAs<uint32_t>(0u);
auto continue_block_id = merge_inst->GetOperandAs<uint32_t>(1u);
if (dest->id() == merge_block_id || dest->id() == continue_block_id) {
return true;
}
} else if (type() == ConstructType::kContinue) {
auto loop_construct = corresponding_constructs()[0];
auto header = loop_construct->entry_block();
auto terminator = header->terminator();
auto index = terminator - &_.ordered_instructions()[0];
auto merge_inst = &_.ordered_instructions()[index - 1];
auto merge_block_id = merge_inst->GetOperandAs<uint32_t>(0u);
if (dest == header || dest->id() == merge_block_id) {
return true;
}
} else {
assert(type() == ConstructType::kSelection);
if (dest == exit_block()) {
return true;
}
auto header = entry_block();
auto block = header;
while (block) {
auto terminator = block->terminator();
auto index = terminator - &_.ordered_instructions()[0];
auto merge_inst = &_.ordered_instructions()[index - 1];
if (merge_inst->opcode() == SpvOpLoopMerge) {
auto merge_target = merge_inst->GetOperandAs<uint32_t>(0u);
auto merge_block = merge_inst->function()->GetBlock(merge_target).first;
if (merge_block->dominates(*header)) {
block = block->immediate_dominator();
continue;
}
auto continue_target = merge_inst->GetOperandAs<uint32_t>(1u);
if (dest->id() == merge_target || dest->id() == continue_target) {
return true;
}
}
block = block->immediate_dominator();
}
}
return false;
}
} // namespace val
} // namespace spvtools

View File

@@ -23,6 +23,7 @@
namespace spvtools {
namespace val {
class ValidationState_t;
/// Functor for ordering BasicBlocks. BasicBlock pointers must not be null.
struct less_than_id {
@@ -109,6 +110,22 @@ class Construct {
// calculated.
ConstructBlockSet blocks(Function* function) const;
// Returns true if |dest| is structured exit from the construct. Structured
// exits depend on the construct type.
// Selection:
// * branch to the associated merge
// * branch to the merge or continue of the innermost loop containing the
// selection
// Loop:
// * branch to the associated merge or continue
// Continue:
// * back-edge to the associated loop header
// * branch to the associated loop merge
//
// Note: the validator does not generate case constructs. Switches are
// checked separately from other constructs.
bool IsStructuredExit(ValidationState_t& _, BasicBlock* dest) const;
private:
/// The type of the construct
ConstructType type_;

View File

@@ -14,10 +14,9 @@
#include "source/val/validate.h"
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <algorithm>
#include <functional>
#include <iterator>
#include <memory>
@@ -52,21 +51,6 @@ namespace spvtools {
namespace val {
namespace {
// TODO(umar): Validate header
// TODO(umar): The binary parser validates the magic word, and the length of the
// header, but nothing else.
spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
uint32_t version, uint32_t generator, uint32_t id_bound,
uint32_t) {
// Record the ID bound so that the validator can ensure no ID is out of bound.
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
_.setIdBound(id_bound);
_.setGenerator(generator);
_.setVersion(version);
return SPV_SUCCESS;
}
// Parses OpExtension instruction and registers extension.
void RegisterExtension(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
@@ -282,7 +266,8 @@ spv_result_t ValidateBinaryUsingContextAndValidationState(
// Parse the module and perform inline validation checks. These checks do
// not require the the knowledge of the whole module.
if (auto error = spvBinaryParse(&context, vstate, words, num_words, setHeader,
if (auto error = spvBinaryParse(&context, vstate, words, num_words,
/*parsed_header =*/nullptr,
ProcessInstruction, pDiagnostic)) {
return error;
}

View File

@@ -185,6 +185,21 @@ std::string LogStringForDecoration(uint32_t decoration) {
return "Unknown";
}
// Returns true if the decoration takes ID parameters.
// TODO(dneto): This can be generated from the grammar.
bool DecorationTakesIdParameters(uint32_t type) {
switch (static_cast<SpvDecoration>(type)) {
case SpvDecorationUniformId:
case SpvDecorationAlignmentId:
case SpvDecorationMaxByteOffsetId:
case SpvDecorationHlslCounterBufferGOOGLE:
return true;
default:
break;
}
return false;
}
spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
const auto decoration = inst->GetOperandAs<uint32_t>(1);
if (decoration == SpvDecorationSpecId) {
@@ -205,10 +220,27 @@ spv_result_t ValidateDecorate(ValidationState_t& _, const Instruction* inst) {
<< "' is not valid for the WebGPU execution environment.";
}
if (DecorationTakesIdParameters(decoration)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Decorations taking ID parameters may not be used with "
"OpDecorateId";
}
// TODO: Add validations for all decorations.
return SPV_SUCCESS;
}
spv_result_t ValidateDecorateId(ValidationState_t& _, const Instruction* inst) {
const auto decoration = inst->GetOperandAs<uint32_t>(1);
if (!DecorationTakesIdParameters(decoration)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Decorations that don't take ID parameters may not be used with "
"OpDecorateId";
}
// TODO: Add validations for these decorations.
// UniformId is covered elsewhere.
return SPV_SUCCESS;
}
spv_result_t ValidateMemberDecorate(ValidationState_t& _,
const Instruction* inst) {
const auto struct_type_id = inst->GetOperandAs<uint32_t>(0);
@@ -340,7 +372,8 @@ spv_result_t ValidateGroupMemberDecorate(ValidationState_t& _,
spv_result_t RegisterDecorations(ValidationState_t& _,
const Instruction* inst) {
switch (inst->opcode()) {
case SpvOpDecorate: {
case SpvOpDecorate:
case SpvOpDecorateId: {
const uint32_t target_id = inst->word(1);
const SpvDecoration dec_type = static_cast<SpvDecoration>(inst->word(2));
std::vector<uint32_t> dec_params;
@@ -415,6 +448,11 @@ spv_result_t AnnotationPass(ValidationState_t& _, const Instruction* inst) {
case SpvOpDecorate:
if (auto error = ValidateDecorate(_, inst)) return error;
break;
case SpvOpDecorateId:
if (auto error = ValidateDecorateId(_, inst)) return error;
break;
// TODO(dneto): SpvOpDecorateStringGOOGLE
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/2253
case SpvOpMemberDecorate:
if (auto error = ValidateMemberDecorate(_, inst)) return error;
break;

View File

@@ -14,8 +14,6 @@
// Validates correctness of barrier SPIR-V instructions.
#include "source/val/validate.h"
#include <string>
#include "source/diagnostic.h"
@@ -24,6 +22,7 @@
#include "source/spirv_target_env.h"
#include "source/util/bitutils.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validate_memory_semantics.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
@@ -38,8 +37,7 @@ spv_result_t BarriersPass(ValidationState_t& _, const Instruction* inst) {
switch (opcode) {
case SpvOpControlBarrier: {
if (spvVersionForTargetEnv(_.context()->target_env) <
SPV_SPIRV_VERSION_WORD(1, 3)) {
if (_.version() < SPV_SPIRV_VERSION_WORD(1, 3)) {
_.function(inst->function()->id())
->RegisterExecutionModelLimitation(
[](SpvExecutionModel model, std::string* message) {

View File

@@ -230,6 +230,78 @@ spv_result_t ValidateReturnValue(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateLoopMerge(ValidationState_t& _, const Instruction* inst) {
const auto merge_id = inst->GetOperandAs<uint32_t>(0);
const auto merge = _.FindDef(merge_id);
if (!merge || merge->opcode() != SpvOpLabel) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Merge Block " << _.getIdName(merge_id) << " must be an OpLabel";
}
const auto continue_id = inst->GetOperandAs<uint32_t>(1);
const auto continue_target = _.FindDef(continue_id);
if (!continue_target || continue_target->opcode() != SpvOpLabel) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Continue Target " << _.getIdName(continue_id)
<< " must be an OpLabel";
}
if (merge_id == continue_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Merge Block and Continue Target must be different ids";
}
const auto loop_control = inst->GetOperandAs<uint32_t>(2);
if ((loop_control >> SpvLoopControlUnrollShift) & 0x1 &&
(loop_control >> SpvLoopControlDontUnrollShift) & 0x1) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Unroll and DontUnroll loop controls must not both be specified";
}
if ((loop_control >> SpvLoopControlDontUnrollShift) & 0x1 &&
(loop_control >> SpvLoopControlPeelCountShift) & 0x1) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PeelCount and DontUnroll "
"loop controls must not "
"both be specified";
}
if ((loop_control >> SpvLoopControlDontUnrollShift) & 0x1 &&
(loop_control >> SpvLoopControlPartialCountShift) & 0x1) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "PartialCount and "
"DontUnroll loop controls "
"must not both be specified";
}
uint32_t operand = 3;
if ((loop_control >> SpvLoopControlDependencyLengthShift) & 0x1) {
++operand;
}
if ((loop_control >> SpvLoopControlMinIterationsShift) & 0x1) {
++operand;
}
if ((loop_control >> SpvLoopControlMaxIterationsShift) & 0x1) {
++operand;
}
if ((loop_control >> SpvLoopControlIterationMultipleShift) & 0x1) {
if (inst->operands().size() < operand ||
inst->GetOperandAs<uint32_t>(operand) == 0) {
return _.diag(SPV_ERROR_INVALID_DATA, inst) << "IterationMultiple loop "
"control operand must be "
"greater than zero";
}
++operand;
}
if ((loop_control >> SpvLoopControlPeelCountShift) & 0x1) {
++operand;
}
if ((loop_control >> SpvLoopControlPartialCountShift) & 0x1) {
++operand;
}
// That the right number of operands is present is checked by the parser. The
// above code tracks operands for expanded validation checking in the future.
return SPV_SUCCESS;
}
} // namespace
void printDominatorList(const BasicBlock& b) {
@@ -580,16 +652,27 @@ spv_result_t StructuredControlFlowChecks(
}
}
// Check that for all non-header blocks, all predecessors are within this
// construct.
Construct::ConstructBlockSet construct_blocks = construct.blocks(function);
for (auto block : construct_blocks) {
std::string construct_name, header_name, exit_name;
std::tie(construct_name, header_name, exit_name) =
ConstructNames(construct.type());
// Check that all exits from the construct are via structured exits.
for (auto succ : *block->successors()) {
if (block->reachable() && !construct_blocks.count(succ) &&
!construct.IsStructuredExit(_, succ)) {
return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(block->id()))
<< "block <ID> " << _.getIdName(block->id()) << " exits the "
<< construct_name << " headed by <ID> "
<< _.getIdName(header->id())
<< ", but not via a structured exit";
}
}
if (block == header) continue;
// Check that for all non-header blocks, all predecessors are within this
// construct.
for (auto pred : *block->predecessors()) {
if (pred->reachable() && !construct_blocks.count(pred)) {
std::string construct_name, header_name, exit_name;
std::tie(construct_name, header_name, exit_name) =
ConstructNames(construct.type());
return _.diag(SPV_ERROR_INVALID_CFG, _.FindDef(pred->id()))
<< "block <ID> " << pred->id() << " branches to the "
<< construct_name << " construct, but not to the "
@@ -608,6 +691,7 @@ spv_result_t StructuredControlFlowChecks(
}
}
}
return SPV_SUCCESS;
}
@@ -764,7 +848,8 @@ spv_result_t PerformCfgChecks(ValidationState_t& _) {
auto edges = CFA<BasicBlock>::CalculateDominators(
postorder, function.AugmentedCFGPredecessorsFunction());
for (auto edge : edges) {
edge.first->SetImmediateDominator(edge.second);
if (edge.first != edge.second)
edge.first->SetImmediateDominator(edge.second);
}
/// calculate post dominators
@@ -931,6 +1016,9 @@ spv_result_t ControlFlowPass(ValidationState_t& _, const Instruction* inst) {
case SpvOpSwitch:
if (auto error = ValidateSwitch(_, inst)) return error;
break;
case SpvOpLoopMerge:
if (auto error = ValidateLoopMerge(_, inst)) return error;
break;
default:
break;
}

View File

@@ -513,6 +513,24 @@ spv_result_t ValidateVectorShuffle(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidateCopyLogical(ValidationState_t& _,
const Instruction* inst) {
const auto result_type = _.FindDef(inst->type_id());
const auto source = _.FindDef(inst->GetOperandAs<uint32_t>(2u));
const auto source_type = _.FindDef(source->type_id());
if (!source_type || !result_type || source_type == result_type) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result Type must not equal the Operand type";
}
if (!_.LogicallyMatch(source_type, result_type, false)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result Type does not logically match the Operand type";
}
return SPV_SUCCESS;
}
} // anonymous namespace
// Validates correctness of composite instructions.
@@ -534,6 +552,8 @@ spv_result_t CompositesPass(ValidationState_t& _, const Instruction* inst) {
return ValidateCopyObject(_, inst);
case SpvOpTranspose:
return ValidateTranspose(_, inst);
case SpvOpCopyLogical:
return ValidateCopyLogical(_, inst);
default:
break;
}

View File

@@ -364,7 +364,8 @@ spv_result_t ValidateSpecConstantOp(ValidationState_t& _,
if (!_.features().uconvert_spec_constant_op &&
!_.HasCapability(SpvCapabilityKernel)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "UConvert requires Kernel capability or extension "
<< "Prior to SPIR-V 1.4, specialization constant operation "
"UConvert requires Kernel capability or extension "
"SPV_AMD_gpu_shader_int16";
}
break;

View File

@@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/val/validate.h"
#include <algorithm>
#include <cassert>
#include <string>
@@ -25,8 +23,10 @@
#include "source/diagnostic.h"
#include "source/opcode.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/spirv_validator_options.h"
#include "source/val/validate_scopes.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -384,6 +384,10 @@ spv_result_t checkLayout(uint32_t struct_id, const char* storage_class_str,
ValidationState_t& vstate) {
if (vstate.options()->skip_block_layout) return SPV_SUCCESS;
// blockRules are the same as bufferBlock rules if the uniform buffer
// standard layout extension is being used.
if (vstate.options()->uniform_buffer_standard_layout) blockRules = false;
// Relaxed layout and scalar layout can both be in effect at the same time.
// For example, relaxed layout is implied by Vulkan 1.1. But scalar layout
// is more permissive than relaxed layout.
@@ -684,6 +688,7 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
int num_builtin_inputs = 0;
int num_builtin_outputs = 0;
for (const auto& desc : descs) {
std::unordered_set<Instruction*> seen_vars;
for (auto interface : desc.interfaces) {
Instruction* var_instr = vstate.FindDef(interface);
if (!var_instr || SpvOpVariable != var_instr->opcode()) {
@@ -694,14 +699,30 @@ spv_result_t CheckDecorationsOfEntryPoints(ValidationState_t& vstate) {
}
const SpvStorageClass storage_class =
var_instr->GetOperandAs<SpvStorageClass>(2);
if (storage_class != SpvStorageClassInput &&
storage_class != SpvStorageClassOutput) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "OpEntryPoint interfaces must be OpVariables with "
"Storage Class of Input(1) or Output(3). Found Storage "
"Class "
<< storage_class << " for Entry Point id " << entry_point
<< ".";
if (vstate.version() >= SPV_SPIRV_VERSION_WORD(1, 4)) {
// Starting in 1.4, OpEntryPoint must list all global variables
// it statically uses and those interfaces must be unique.
if (storage_class == SpvStorageClassFunction) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "OpEntryPoint interfaces should only list global "
"variables";
}
if (!seen_vars.insert(var_instr).second) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "Non-unique OpEntryPoint interface "
<< vstate.getIdName(interface) << " is disallowed";
}
} else {
if (storage_class != SpvStorageClassInput &&
storage_class != SpvStorageClassOutput) {
return vstate.diag(SPV_ERROR_INVALID_ID, var_instr)
<< "OpEntryPoint interfaces must be OpVariables with "
"Storage Class of Input(1) or Output(3). Found Storage "
"Class "
<< storage_class << " for Entry Point id " << entry_point
<< ".";
}
}
const uint32_t ptr_id = var_instr->word(1);
@@ -964,10 +985,12 @@ spv_result_t CheckDecorationsOfBuffers(ValidationState_t& vstate) {
vstate.RegisterPointerToUniformBlock(ptrInst->id());
vstate.RegisterStructForUniformBlock(id);
}
if ((uniform && bufferDeco) || (storage_buffer && blockDeco)) {
if ((uniform && bufferDeco) ||
((storage_buffer || phys_storage_buffer) && blockDeco)) {
vstate.RegisterPointerToStorageBuffer(ptrInst->id());
vstate.RegisterStructForStorageBuffer(id);
}
if (blockRules || bufferRules) {
const char* deco_str = blockDeco ? "Block" : "BufferBlock";
spv_result_t recursive_status = SPV_SUCCESS;
@@ -1257,34 +1280,51 @@ spv_result_t CheckNonWritableDecoration(ValidationState_t& vstate,
if (decoration.struct_member_index() == Decoration::kInvalidMember) {
// The target must be a memory object declaration.
// First, it must be a variable or function parameter.
if (inst.opcode() != SpvOpVariable &&
inst.opcode() != SpvOpFunctionParameter) {
const auto opcode = inst.opcode();
const auto type_id = inst.type_id();
if (opcode != SpvOpVariable && opcode != SpvOpFunctionParameter) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Target of NonWritable decoration must be a memory object "
"declaration (a variable or a function parameter)";
}
// Second, it must point to a UBO, SSBO, or storage image.
const auto type_id = inst.type_id();
if (!vstate.IsPointerToUniformBlock(type_id) &&
!vstate.IsPointerToStorageBuffer(type_id) &&
!vstate.IsPointerToStorageImage(type_id)) {
const auto var_storage_class = opcode == SpvOpVariable
? inst.GetOperandAs<SpvStorageClass>(2)
: SpvStorageClassMax;
if ((var_storage_class == SpvStorageClassFunction ||
var_storage_class == SpvStorageClassPrivate) &&
vstate.features().nonwritable_var_in_function_or_private) {
// New permitted feature in SPIR-V 1.4.
} else if (
// It may point to a UBO, SSBO, or storage image.
vstate.IsPointerToUniformBlock(type_id) ||
vstate.IsPointerToStorageBuffer(type_id) ||
vstate.IsPointerToStorageImage(type_id)) {
} else {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Target of NonWritable decoration is invalid: must point to a "
"storage image, uniform block, or storage buffer";
"storage image, uniform block, "
<< (vstate.features().nonwritable_var_in_function_or_private
? "storage buffer, or variable in Private or Function "
"storage class"
: "or storage buffer");
}
}
return SPV_SUCCESS;
}
// Returns SPV_SUCCESS if validation rules are satisfied for Uniform
// decorations. Otherwise emits a diagnostic and returns something other than
// SPV_SUCCESS. Assumes each decoration on a group has been propagated down to
// the group members.
// Returns SPV_SUCCESS if validation rules are satisfied for Uniform or
// UniformId decorations. Otherwise emits a diagnostic and returns something
// other than SPV_SUCCESS. Assumes each decoration on a group has been
// propagated down to the group members. The |inst| parameter is the object
// being decorated.
spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
const Instruction& inst,
const Decoration&) {
// Uniform must decorate an "object"
const Decoration& decoration) {
const char* const dec_name =
decoration.dec_type() == SpvDecorationUniform ? "Uniform" : "UniformId";
// Uniform or UniformId must decorate an "object"
// - has a result ID
// - is an instantiation of a non-void type. So it has a type ID, and that
// type is not void.
@@ -1293,19 +1333,33 @@ spv_result_t CheckUniformDecoration(ValidationState_t& vstate,
if (inst.type_id() == 0) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Uniform decoration applied to a non-object";
<< dec_name << " decoration applied to a non-object";
}
if (Instruction* type_inst = vstate.FindDef(inst.type_id())) {
if (type_inst->opcode() == SpvOpTypeVoid) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Uniform decoration applied to a value with void type";
<< dec_name << " decoration applied to a value with void type";
}
} else {
// We might never get here because this would have been rejected earlier in
// the flow.
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)
<< "Uniform decoration applied to an object with invalid type";
<< dec_name << " decoration applied to an object with invalid type";
}
// Use of Uniform with OpDecorate is checked elsewhere.
// Use of UniformId with OpDecorateId is checked elsewhere.
if (decoration.dec_type() == SpvDecorationUniformId) {
assert(decoration.params().size() == 1 &&
"Grammar ensures UniformId has one parameter");
// The scope id is an execution scope.
if (auto error =
ValidateExecutionScope(vstate, &inst, decoration.params()[0]))
return error;
}
return SPV_SUCCESS;
}
@@ -1365,7 +1419,6 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
// been propagated down to the group members.
if (inst->opcode() == SpvOpDecorationGroup) continue;
// Validates FPRoundingMode decoration
for (const auto& decoration : decorations) {
switch (decoration.dec_type()) {
case SpvDecorationFPRoundingMode:
@@ -1376,6 +1429,7 @@ spv_result_t CheckDecorationsFromDecoration(ValidationState_t& vstate) {
PASS_OR_BAIL(CheckNonWritableDecoration(vstate, *inst, decoration));
break;
case SpvDecorationUniform:
case SpvDecorationUniformId:
PASS_OR_BAIL(CheckUniformDecoration(vstate, *inst, decoration));
break;
case SpvDecorationNoSignedWrap:

View File

@@ -12,27 +12,45 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/val/validate.h"
#include <algorithm>
#include "source/opcode.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
namespace spvtools {
namespace val {
namespace {
// Returns true if |a| and |b| are instruction defining pointers that point to
// the same type.
bool ArePointersToSameType(val::Instruction* a, val::Instruction* b) {
// Returns true if |a| and |b| are instructions defining pointers that point to
// types logically match and the decorations that apply to |b| are a subset
// of the decorations that apply to |a|.
bool DoPointeesLogicallyMatch(val::Instruction* a, val::Instruction* b,
ValidationState_t& _) {
if (a->opcode() != SpvOpTypePointer || b->opcode() != SpvOpTypePointer) {
return false;
}
const auto& dec_a = _.id_decorations(a->id());
const auto& dec_b = _.id_decorations(b->id());
for (const auto& dec : dec_b) {
if (std::find(dec_a.begin(), dec_a.end(), dec) == dec_a.end()) {
return false;
}
}
uint32_t a_type = a->GetOperandAs<uint32_t>(2);
return a_type && (a_type == b->GetOperandAs<uint32_t>(2));
uint32_t b_type = b->GetOperandAs<uint32_t>(2);
if (a_type == b_type) {
return true;
}
Instruction* a_type_inst = _.FindDef(a_type);
Instruction* b_type_inst = _.FindDef(b_type);
return _.LogicallyMatch(a_type_inst, b_type_inst, true);
}
spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
@@ -256,14 +274,14 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
const auto parameter_type_id =
function_type->GetOperandAs<uint32_t>(param_index);
const auto parameter_type = _.FindDef(parameter_type_id);
if (!parameter_type ||
(argument_type->id() != parameter_type->id() &&
!(_.options()->relax_logical_pointer &&
ArePointersToSameType(argument_type, parameter_type)))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpFunctionCall Argument <id> '" << _.getIdName(argument_id)
<< "'s type does not match Function <id> '"
<< _.getIdName(parameter_type_id) << "'s parameter type.";
if (!parameter_type || argument_type->id() != parameter_type->id()) {
if (!_.options()->before_hlsl_legalization ||
!DoPointeesLogicallyMatch(argument_type, parameter_type, _)) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpFunctionCall Argument <id> '" << _.getIdName(argument_id)
<< "'s type does not match Function <id> '"
<< _.getIdName(parameter_type_id) << "'s parameter type.";
}
}
if (_.addressing_model() == SpvAddressingModelLogical) {

View File

@@ -63,6 +63,8 @@ bool CheckAllImageOperandsHandled() {
case SpvImageOperandsMakeTexelVisibleKHRMask:
case SpvImageOperandsNonPrivateTexelKHRMask:
case SpvImageOperandsVolatileTexelKHRMask:
case SpvImageOperandsSignExtendMask:
case SpvImageOperandsZeroExtendMask:
return true;
}
return false;
@@ -218,10 +220,12 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
const SpvOp opcode = inst->opcode();
const size_t num_words = inst->words().size();
// NonPrivate and Volatile take no operand words.
// NonPrivate, Volatile, SignExtend, ZeroExtend take no operand words.
const uint32_t mask_bits_having_operands =
mask & ~uint32_t(SpvImageOperandsNonPrivateTexelKHRMask |
SpvImageOperandsVolatileTexelKHRMask);
SpvImageOperandsVolatileTexelKHRMask |
SpvImageOperandsSignExtendMask |
SpvImageOperandsZeroExtendMask);
size_t expected_num_image_operand_words =
spvtools::utils::CountSetBits(mask_bits_having_operands);
if (mask & SpvImageOperandsGradMask) {
@@ -541,6 +545,32 @@ spv_result_t ValidateImageOperands(ValidationState_t& _,
if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
}
if (mask & SpvImageOperandsSignExtendMask) {
// Checked elsewhere: SPIR-V 1.4 version or later.
// "The texel value is converted to the target value via sign extension.
// Only valid when the texel type is a scalar or vector of integer type."
//
// We don't have enough information to know what the texel type is.
// In OpenCL, knowledge is deferred until runtime: the image SampledType is
// void, and the Format is Unknown.
// In Vulkan, the texel type is only known in all cases by the pipeline
// setup.
}
if (mask & SpvImageOperandsZeroExtendMask) {
// Checked elsewhere: SPIR-V 1.4 version or later.
// "The texel value is converted to the target value via zero extension.
// Only valid when the texel type is a scalar or vector of integer type."
//
// We don't have enough information to know what the texel type is.
// In OpenCL, knowledge is deferred until runtime: the image SampledType is
// void, and the Format is Unknown.
// In Vulkan, the texel type is only known in all cases by the pipeline
// setup.
}
return SPV_SUCCESS;
}

View File

@@ -14,10 +14,9 @@
// Performs validation on instructions that appear inside of a SPIR-V block.
#include "source/val/validate.h"
#include <algorithm>
#include <cassert>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
@@ -35,6 +34,7 @@
#include "source/spirv_validator_options.h"
#include "source/util/string_utils.h"
#include "source/val/function.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -98,21 +98,72 @@ CapabilitySet EnablingCapabilitiesForOp(const ValidationState_t& state,
return CapabilitySet();
}
// Returns SPV_SUCCESS if, for the given operand, the target environment
// satsifies minimum version requirements, or if the module declares an
// enabling extension for the operand. Otherwise emit a diagnostic and
// return an error code.
spv_result_t OperandVersionExtensionCheck(
ValidationState_t& _, const Instruction* inst, size_t which_operand,
const spv_operand_desc_t& operand_desc, uint32_t word) {
const uint32_t module_version = _.version();
const uint32_t operand_min_version = operand_desc.minVersion;
const uint32_t operand_last_version = operand_desc.lastVersion;
const bool reserved = operand_min_version == 0xffffffffu;
const bool version_satisfied = !reserved &&
(operand_min_version <= module_version) &&
(module_version <= operand_last_version);
if (version_satisfied) {
return SPV_SUCCESS;
}
if (operand_last_version < module_version) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvtools::utils::CardinalToOrdinal(which_operand)
<< " operand of " << spvOpcodeString(inst->opcode()) << ": operand "
<< operand_desc.name << "(" << word << ") requires SPIR-V version "
<< SPV_SPIRV_VERSION_MAJOR_PART(operand_last_version) << "."
<< SPV_SPIRV_VERSION_MINOR_PART(operand_last_version)
<< " or earlier";
}
if (!reserved && operand_desc.numExtensions == 0) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvtools::utils::CardinalToOrdinal(which_operand)
<< " operand of " << spvOpcodeString(inst->opcode()) << ": operand "
<< operand_desc.name << "(" << word << ") requires SPIR-V version "
<< SPV_SPIRV_VERSION_MAJOR_PART(operand_min_version) << "."
<< SPV_SPIRV_VERSION_MINOR_PART(operand_min_version) << " or later";
} else {
ExtensionSet required_extensions(operand_desc.numExtensions,
operand_desc.extensions);
if (!_.HasAnyOfExtensions(required_extensions)) {
return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
<< spvtools::utils::CardinalToOrdinal(which_operand)
<< " operand of " << spvOpcodeString(inst->opcode())
<< ": operand " << operand_desc.name << "(" << word
<< ") requires one of these extensions: "
<< ExtensionSetToString(required_extensions);
}
}
return SPV_SUCCESS;
}
// Returns SPV_SUCCESS if the given operand is enabled by capabilities declared
// in the module. Otherwise issues an error message and returns
// SPV_ERROR_INVALID_CAPABILITY.
spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
const Instruction* inst,
size_t which_operand,
spv_operand_type_t type,
uint32_t operand) {
const spv_parsed_operand_t& operand,
uint32_t word) {
// Mere mention of PointSize, ClipDistance, or CullDistance in a Builtin
// decoration does not require the associated capability. The use of such
// a variable value should trigger the capability requirement, but that's
// not implemented yet. This rule is independent of target environment.
// See https://github.com/KhronosGroup/SPIRV-Tools/issues/365
if (type == SPV_OPERAND_TYPE_BUILT_IN) {
switch (operand) {
if (operand.type == SPV_OPERAND_TYPE_BUILT_IN) {
switch (word) {
case SpvBuiltInPointSize:
case SpvBuiltInClipDistance:
case SpvBuiltInCullDistance:
@@ -120,14 +171,14 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
default:
break;
}
} else if (type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
} else if (operand.type == SPV_OPERAND_TYPE_FP_ROUNDING_MODE) {
// Allow all FP rounding modes if requested
if (state.features().free_fp_rounding_mode) {
return SPV_SUCCESS;
}
} else if (type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
} else if (operand.type == SPV_OPERAND_TYPE_GROUP_OPERATION &&
state.features().group_ops_reduce_and_scans &&
(operand <= uint32_t(SpvGroupOperationExclusiveScan))) {
(word <= uint32_t(SpvGroupOperationExclusiveScan))) {
// Allow certain group operations if requested.
return SPV_SUCCESS;
}
@@ -135,10 +186,10 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
CapabilitySet enabling_capabilities;
spv_operand_desc operand_desc = nullptr;
const auto lookup_result =
state.grammar().lookupOperand(type, operand, &operand_desc);
state.grammar().lookupOperand(operand.type, word, &operand_desc);
if (lookup_result == SPV_SUCCESS) {
// Allow FPRoundingMode decoration if requested.
if (type == SPV_OPERAND_TYPE_DECORATION &&
if (operand.type == SPV_OPERAND_TYPE_DECORATION &&
operand_desc->value == SpvDecorationFPRoundingMode) {
if (state.features().free_fp_rounding_mode) return SPV_SUCCESS;
@@ -161,29 +212,13 @@ spv_result_t CheckRequiredCapabilities(ValidationState_t& state,
<< " requires one of these capabilities: "
<< ToString(enabling_capabilities, state.grammar());
}
return OperandVersionExtensionCheck(state, inst, which_operand,
*operand_desc, word);
}
return SPV_SUCCESS;
}
// Returns operand's required extensions.
ExtensionSet RequiredExtensions(const ValidationState_t& state,
spv_operand_type_t type, uint32_t operand) {
spv_operand_desc operand_desc;
if (state.grammar().lookupOperand(type, operand, &operand_desc) ==
SPV_SUCCESS) {
assert(operand_desc);
// If this operand is incorporated into core SPIR-V before or in the current
// target environment, we don't require extensions anymore.
if (spvVersionForTargetEnv(state.grammar().target_env()) >=
operand_desc->minVersion)
return {};
return {operand_desc->numExtensions, operand_desc->extensions};
}
return {};
}
// Returns SPV_ERROR_INVALID_BINARY and emits a diagnostic if the instruction
// is explicitly reserved in the SPIR-V core spec. Otherwise return
// SPV_SUCCESS.
@@ -244,7 +279,7 @@ spv_result_t CapabilityCheck(ValidationState_t& _, const Instruction* inst) {
for (uint32_t mask_bit = 0x80000000; mask_bit; mask_bit >>= 1) {
if (word & mask_bit) {
spv_result_t status =
CheckRequiredCapabilities(_, inst, i + 1, operand.type, mask_bit);
CheckRequiredCapabilities(_, inst, i + 1, operand, mask_bit);
if (status != SPV_SUCCESS) return status;
}
}
@@ -255,34 +290,13 @@ spv_result_t CapabilityCheck(ValidationState_t& _, const Instruction* inst) {
} else {
// Check the operand word as a whole.
spv_result_t status =
CheckRequiredCapabilities(_, inst, i + 1, operand.type, word);
CheckRequiredCapabilities(_, inst, i + 1, operand, word);
if (status != SPV_SUCCESS) return status;
}
}
return SPV_SUCCESS;
}
// Checks that all extensions required by the given instruction's operands were
// declared in the module.
spv_result_t ExtensionCheck(ValidationState_t& _, const Instruction* inst) {
const SpvOp opcode = inst->opcode();
for (size_t operand_index = 0; operand_index < inst->operands().size();
++operand_index) {
const auto& operand = inst->operand(operand_index);
const uint32_t word = inst->word(operand.offset);
const ExtensionSet required_extensions =
RequiredExtensions(_, operand.type, word);
if (!_.HasAnyOfExtensions(required_extensions)) {
return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
<< spvtools::utils::CardinalToOrdinal(operand_index + 1)
<< " operand of " << spvOpcodeString(opcode) << ": operand "
<< word << " requires one of these extensions: "
<< ExtensionSetToString(required_extensions);
}
}
return SPV_SUCCESS;
}
// Checks that the instruction can be used in this target environment's base
// version. Assumes that CapabilityCheck has checked direct capability
// dependencies for the opcode.
@@ -294,6 +308,15 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) {
(void)r;
const auto min_version = inst_desc->minVersion;
const auto last_version = inst_desc->lastVersion;
const auto module_version = _.version();
if (last_version < module_version) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvOpcodeString(opcode) << " requires SPIR-V version "
<< SPV_SPIRV_VERSION_MAJOR_PART(last_version) << "."
<< SPV_SPIRV_VERSION_MINOR_PART(last_version) << " or earlier";
}
if (inst_desc->numCapabilities > 0u) {
// We already checked that the direct capability dependency has been
@@ -303,22 +326,23 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) {
ExtensionSet exts(inst_desc->numExtensions, inst_desc->extensions);
if (exts.IsEmpty()) {
// If no extensions can enable this instruction, then emit error messages
// only concerning core SPIR-V versions if errors happen.
// If no extensions can enable this instruction, then emit error
// messages only concerning core SPIR-V versions if errors happen.
if (min_version == ~0u) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvOpcodeString(opcode) << " is reserved for future use.";
}
if (spvVersionForTargetEnv(_.grammar().target_env()) < min_version) {
if (module_version < min_version) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvOpcodeString(opcode) << " requires "
<< spvTargetEnvDescription(
static_cast<spv_target_env>(min_version))
<< " at minimum.";
}
// Otherwise, we only error out when no enabling extensions are registered.
} else if (!_.HasAnyOfExtensions(exts)) {
// Otherwise, we only error out when no enabling extensions are
// registered.
if (min_version == ~0u) {
return _.diag(SPV_ERROR_MISSING_EXTENSION, inst)
<< spvOpcodeString(opcode)
@@ -326,11 +350,11 @@ spv_result_t VersionCheck(ValidationState_t& _, const Instruction* inst) {
<< ExtensionSetToString(exts);
}
if (static_cast<uint32_t>(_.grammar().target_env()) < min_version) {
if (module_version < min_version) {
return _.diag(SPV_ERROR_WRONG_VERSION, inst)
<< spvOpcodeString(opcode) << " requires "
<< spvTargetEnvDescription(
static_cast<spv_target_env>(min_version))
<< spvOpcodeString(opcode) << " requires SPIR-V version "
<< SPV_SPIRV_VERSION_MAJOR_PART(min_version) << "."
<< SPV_SPIRV_VERSION_MINOR_PART(min_version)
<< " at minimum or one of the following extensions: "
<< ExtensionSetToString(exts);
}
@@ -367,11 +391,10 @@ spv_result_t LimitCheckStruct(ValidationState_t& _, const Instruction* inst) {
// Section 2.17 of SPIRV Spec specifies that the "Structure Nesting Depth"
// must be less than or equal to 255.
// This is interpreted as structures including other structures as members.
// The code does not follow pointers or look into arrays to see if we reach a
// structure downstream.
// The nesting depth of a struct is 1+(largest depth of any member).
// Scalars are at depth 0.
// This is interpreted as structures including other structures as
// members. The code does not follow pointers or look into arrays to see
// if we reach a structure downstream. The nesting depth of a struct is
// 1+(largest depth of any member). Scalars are at depth 0.
uint32_t max_member_depth = 0;
// Struct members start at word 2 of OpTypeStruct instruction.
for (size_t word_i = 2; word_i < inst->words().size(); ++word_i) {
@@ -394,8 +417,8 @@ spv_result_t LimitCheckStruct(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
// Checks that the number of (literal, label) pairs in OpSwitch is within the
// limit.
// Checks that the number of (literal, label) pairs in OpSwitch is within
// the limit.
spv_result_t LimitCheckSwitch(ValidationState_t& _, const Instruction* inst) {
if (SpvOpSwitch == inst->opcode()) {
// The instruction syntax is as follows:
@@ -414,7 +437,8 @@ spv_result_t LimitCheckSwitch(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
// Ensure the number of variables of the given class does not exceed the limit.
// Ensure the number of variables of the given class does not exceed the
// limit.
spv_result_t LimitCheckNumVars(ValidationState_t& _, const uint32_t var_id,
const SpvStorageClass storage_class) {
if (SpvStorageClassFunction == storage_class) {
@@ -472,7 +496,8 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) {
if (_.memory_model() != SpvMemoryModelVulkanKHR &&
_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "VulkanMemoryModelKHR capability must only be specified if the "
<< "VulkanMemoryModelKHR capability must only be specified if "
"the "
"VulkanKHR memory model is used.";
}
@@ -486,6 +511,19 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) {
<< "Memory model must be VulkanKHR for WebGPU environment.";
}
}
if (spvIsOpenCLEnv(_.context()->target_env)) {
if ((_.addressing_model() != SpvAddressingModelPhysical32) &&
(_.addressing_model() != SpvAddressingModelPhysical64)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Addressing model must be Physical32 or Physical64 "
<< "in the OpenCL environment.";
}
if (_.memory_model() != SpvMemoryModelOpenCL) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Memory model must be OpenCL in the OpenCL environment.";
}
}
} else if (opcode == SpvOpExecutionMode) {
const uint32_t entry_point = inst->word(1);
_.RegisterExecutionModeForEntryPoint(entry_point,
@@ -548,7 +586,6 @@ spv_result_t InstructionPass(ValidationState_t& _, const Instruction* inst) {
"capability is used.";
}
if (auto error = ExtensionCheck(_, inst)) return error;
if (auto error = ReservedCheck(_, inst)) return error;
if (auto error = EnvironmentCheck(_, inst)) return error;
if (auto error = CapabilityCheck(_, inst)) return error;

View File

@@ -12,14 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/val/validate.h"
#include <algorithm>
#include <vector>
#include "source/diagnostic.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/val/function.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -27,10 +28,16 @@ namespace val {
namespace {
// Returns true if \c inst is an input or output variable.
bool is_interface_variable(const Instruction* inst) {
return inst->opcode() == SpvOpVariable &&
(inst->word(3u) == SpvStorageClassInput ||
inst->word(3u) == SpvStorageClassOutput);
bool is_interface_variable(const Instruction* inst, bool is_spv_1_4) {
if (is_spv_1_4) {
// Starting in SPIR-V 1.4, all global variables are interface variables.
return inst->opcode() == SpvOpVariable &&
inst->word(3u) != SpvStorageClassFunction;
} else {
return inst->opcode() == SpvOpVariable &&
(inst->word(3u) == SpvStorageClassInput ||
inst->word(3u) == SpvStorageClassOutput);
}
}
// Checks that \c var is listed as an interface in all the entry points that use
@@ -85,9 +92,8 @@ spv_result_t check_interface_variable(ValidationState_t& _,
}
if (!found) {
return _.diag(SPV_ERROR_INVALID_ID, var)
<< (var->word(3u) == SpvStorageClassInput ? "Input" : "Output")
<< " variable id <" << var->id() << "> is used by entry point '"
<< desc.name << "' id <" << id
<< "Interface variable id <" << var->id()
<< "> is used by entry point '" << desc.name << "' id <" << id
<< ">, but is not listed as an interface";
}
}
@@ -99,8 +105,9 @@ spv_result_t check_interface_variable(ValidationState_t& _,
} // namespace
spv_result_t ValidateInterfaces(ValidationState_t& _) {
bool is_spv_1_4 = _.version() >= SPV_SPIRV_VERSION_WORD(1, 4);
for (auto& inst : _.ordered_instructions()) {
if (is_interface_variable(&inst)) {
if (is_interface_variable(&inst, is_spv_1_4)) {
if (auto error = check_interface_variable(_, &inst)) {
return error;
}

View File

@@ -151,6 +151,14 @@ spv_result_t LogicalsPass(ValidationState_t& _, const Instruction* inst) {
const Instruction* type_inst = _.FindDef(result_type);
assert(type_inst);
const auto composites = _.features().select_between_composites;
auto fail = [&_, composites, inst, opcode]() -> spv_result_t {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected scalar or "
<< (composites ? "composite" : "vector")
<< " type as Result Type: " << spvOpcodeString(opcode);
};
const SpvOp type_opcode = type_inst->opcode();
switch (type_opcode) {
case SpvOpTypePointer: {
@@ -174,35 +182,48 @@ spv_result_t LogicalsPass(ValidationState_t& _, const Instruction* inst) {
break;
}
default: {
// Not RuntimeArray because of other rules.
case SpvOpTypeArray:
case SpvOpTypeMatrix:
case SpvOpTypeStruct: {
if (!composites) return fail();
break;
};
default:
return fail();
}
const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
const uint32_t left_type = _.GetOperandTypeId(inst, 3);
const uint32_t right_type = _.GetOperandTypeId(inst, 4);
if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
!_.IsBoolVectorType(condition_type)))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected bool scalar or vector type as condition: "
<< spvOpcodeString(opcode);
if (_.GetDimension(condition_type) != dimension) {
// If the condition is a vector type, then the result must also be a
// vector with matching dimensions. In SPIR-V 1.4, a scalar condition
// can be used to select between vector types. |composites| is a
// proxy for SPIR-V 1.4 functionality.
if (!composites || _.IsBoolVectorType(condition_type)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected scalar or vector type as Result Type: "
<< "Expected vector sizes of Result Type and the condition "
"to be equal: "
<< spvOpcodeString(opcode);
}
}
if (result_type != left_type || result_type != right_type)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected both objects to be of Result Type: "
<< spvOpcodeString(opcode);
break;
}
const uint32_t condition_type = _.GetOperandTypeId(inst, 2);
const uint32_t left_type = _.GetOperandTypeId(inst, 3);
const uint32_t right_type = _.GetOperandTypeId(inst, 4);
if (!condition_type || (!_.IsBoolScalarType(condition_type) &&
!_.IsBoolVectorType(condition_type)))
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected bool scalar or vector type as condition: "
<< spvOpcodeString(opcode);
if (_.GetDimension(condition_type) != dimension)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected vector sizes of Result Type and the condition to be"
<< " equal: " << spvOpcodeString(opcode);
if (result_type != left_type || result_type != right_type)
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Expected both objects to be of Result Type: "
<< spvOpcodeString(opcode);
break;
}
case SpvOpIEqual:

View File

@@ -263,59 +263,40 @@ std::pair<SpvStorageClass, SpvStorageClass> GetStorageClass(
return std::make_pair(dst_sc, src_sc);
}
// Returns the number of instruction words taken up by a memory access
// argument and its implied operands.
int MemoryAccessNumWords(uint32_t mask) {
int result = 1; // Count the mask
if (mask & SpvMemoryAccessAlignedMask) ++result;
if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++result;
if (mask & SpvMemoryAccessMakePointerVisibleKHRMask) ++result;
return result;
}
// Returns the scope ID operand for MakeAvailable memory access with mask
// at the given operand index.
// This function is only called for OpLoad, OpStore, OpCopyMemory and
// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
// OpCooperativeMatrixStoreNV.
uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask) {
uint32_t offset = 1;
if (mask & SpvMemoryAccessAlignedMask) ++offset;
uint32_t scope_id = 0;
switch (inst->opcode()) {
case SpvOpLoad:
case SpvOpCopyMemorySized:
return inst->GetOperandAs<uint32_t>(3 + offset);
case SpvOpStore:
case SpvOpCopyMemory:
return inst->GetOperandAs<uint32_t>(2 + offset);
case SpvOpCooperativeMatrixLoadNV:
return inst->GetOperandAs<uint32_t>(5 + offset);
case SpvOpCooperativeMatrixStoreNV:
return inst->GetOperandAs<uint32_t>(4 + offset);
default:
assert(false && "unexpected opcode");
break;
}
return scope_id;
uint32_t GetMakeAvailableScope(const Instruction* inst, uint32_t mask,
uint32_t mask_index) {
assert(mask & SpvMemoryAccessMakePointerAvailableKHRMask);
uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerAvailableKHRMask);
uint32_t index =
mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
return inst->GetOperandAs<uint32_t>(index);
}
// This function is only called for OpLoad, OpStore, OpCopyMemory,
// OpCopyMemorySized, OpCooperativeMatrixLoadNV, and
// OpCooperativeMatrixStoreNV.
uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask) {
uint32_t offset = 1;
if (mask & SpvMemoryAccessAlignedMask) ++offset;
if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) ++offset;
uint32_t scope_id = 0;
switch (inst->opcode()) {
case SpvOpLoad:
case SpvOpCopyMemorySized:
return inst->GetOperandAs<uint32_t>(3 + offset);
case SpvOpStore:
case SpvOpCopyMemory:
return inst->GetOperandAs<uint32_t>(2 + offset);
case SpvOpCooperativeMatrixLoadNV:
return inst->GetOperandAs<uint32_t>(5 + offset);
case SpvOpCooperativeMatrixStoreNV:
return inst->GetOperandAs<uint32_t>(4 + offset);
default:
assert(false && "unexpected opcode");
break;
}
return scope_id;
uint32_t GetMakeVisibleScope(const Instruction* inst, uint32_t mask,
uint32_t mask_index) {
assert(mask & SpvMemoryAccessMakePointerVisibleKHRMask);
uint32_t this_bit = uint32_t(SpvMemoryAccessMakePointerVisibleKHRMask);
uint32_t index =
mask_index - 1 + MemoryAccessNumWords(mask & (this_bit | (this_bit - 1)));
return inst->GetOperandAs<uint32_t>(index);
}
bool DoesStructContainRTA(const ValidationState_t& _, const Instruction* inst) {
@@ -342,7 +323,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
return SPV_SUCCESS;
}
uint32_t mask = inst->GetOperandAs<uint32_t>(index);
const uint32_t mask = inst->GetOperandAs<uint32_t>(index);
if (mask & SpvMemoryAccessMakePointerAvailableKHRMask) {
if (inst->opcode() == SpvOpLoad ||
inst->opcode() == SpvOpCooperativeMatrixLoadNV) {
@@ -357,7 +338,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
}
// Check the associated scope for MakeAvailableKHR.
const auto available_scope = GetMakeAvailableScope(inst, mask);
const auto available_scope = GetMakeAvailableScope(inst, mask, index);
if (auto error = ValidateMemoryScope(_, inst, available_scope))
return error;
}
@@ -376,7 +357,7 @@ spv_result_t CheckMemoryAccess(ValidationState_t& _, const Instruction* inst,
}
// Check the associated scope for MakeVisibleKHR.
const auto visible_scope = GetMakeVisibleScope(inst, mask);
const auto visible_scope = GetMakeVisibleScope(inst, mask, index);
if (auto error = ValidateMemoryScope(_, inst, visible_scope)) return error;
}
@@ -863,6 +844,51 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
spv_result_t ValidateCopyMemoryMemoryAccess(ValidationState_t& _,
const Instruction* inst) {
assert(inst->opcode() == SpvOpCopyMemory ||
inst->opcode() == SpvOpCopyMemorySized);
const uint32_t first_access_index = inst->opcode() == SpvOpCopyMemory ? 2 : 3;
if (inst->operands().size() > first_access_index) {
if (auto error = CheckMemoryAccess(_, inst, first_access_index))
return error;
const auto first_access = inst->GetOperandAs<uint32_t>(first_access_index);
const uint32_t second_access_index =
first_access_index + MemoryAccessNumWords(first_access);
if (inst->operands().size() > second_access_index) {
if (_.features().copy_memory_permits_two_memory_accesses) {
if (auto error = CheckMemoryAccess(_, inst, second_access_index))
return error;
// In the two-access form in SPIR-V 1.4 and later:
// - the first is the target (write) access and it can't have
// make-visible.
// - the second is the source (read) access and it can't have
// make-available.
if (first_access & SpvMemoryAccessMakePointerVisibleKHRMask) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Target memory access must not include "
"MakePointerVisibleKHR";
}
const auto second_access =
inst->GetOperandAs<uint32_t>(second_access_index);
if (second_access & SpvMemoryAccessMakePointerAvailableKHRMask) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Source memory access must not include "
"MakePointerAvailableKHR";
}
} else {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< spvOpcodeString(static_cast<SpvOp>(inst->opcode()))
<< " with two memory access operands requires SPIR-V 1.4 or "
"later";
}
}
}
return SPV_SUCCESS;
}
spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
const auto target_index = 0;
const auto target_id = inst->GetOperandAs<uint32_t>(target_index);
@@ -968,7 +994,7 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) {
if (auto error = CheckMemoryAccess(_, inst, 3)) return error;
}
return SPV_SUCCESS;
return ValidateCopyMemoryMemoryAccess(_, inst);
}
spv_result_t ValidateAccessChain(ValidationState_t& _,
@@ -1326,6 +1352,57 @@ spv_result_t ValidateCooperativeMatrixLoadStoreNV(ValidationState_t& _,
return SPV_SUCCESS;
}
spv_result_t ValidatePtrComparison(ValidationState_t& _,
const Instruction* inst) {
if (_.addressing_model() == SpvAddressingModelLogical &&
!_.features().variable_pointers_storage_buffer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Instruction cannot be used without a variable pointers "
"capability";
}
const auto result_type = _.FindDef(inst->type_id());
if (inst->opcode() == SpvOpPtrDiff) {
if (!result_type || result_type->opcode() != SpvOpTypeInt) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result Type must be an integer scalar";
}
} else {
if (!result_type || result_type->opcode() != SpvOpTypeBool) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result Type must be OpTypeBool";
}
}
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());
if (!op1_type || op1_type->opcode() != SpvOpTypePointer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand type must be a pointer";
}
if (_.addressing_model() == SpvAddressingModelLogical) {
SpvStorageClass sc = op1_type->GetOperandAs<SpvStorageClass>(1u);
if (sc != SpvStorageClassWorkgroup && sc != SpvStorageClassStorageBuffer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Invalid pointer storage class";
}
if (sc == SpvStorageClassWorkgroup && !_.features().variable_pointers) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Workgroup storage class pointer requires VariablePointers "
"capability to be specified";
}
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
@@ -1362,6 +1439,11 @@ spv_result_t MemoryPass(ValidationState_t& _, const Instruction* inst) {
case SpvOpCooperativeMatrixLengthNV:
if (auto error = ValidateCooperativeMatrixLengthNV(_, inst)) return error;
break;
case SpvOpPtrEqual:
case SpvOpPtrNotEqual:
case SpvOpPtrDiff:
if (auto error = ValidatePtrComparison(_, inst)) return error;
break;
case SpvOpImageTexelPointer:
case SpvOpGenericPtrMemSemantics:
default:

View File

@@ -22,6 +22,23 @@
namespace spvtools {
namespace val {
bool IsValidScope(uint32_t scope) {
// Deliberately avoid a default case so we have to update the list when the
// scopes list changes.
switch (static_cast<SpvScope>(scope)) {
case SpvScopeCrossDevice:
case SpvScopeDevice:
case SpvScopeWorkgroup:
case SpvScopeSubgroup:
case SpvScopeInvocation:
case SpvScopeQueueFamilyKHR:
return true;
case SpvScopeMax:
break;
}
return false;
}
spv_result_t ValidateExecutionScope(ValidationState_t& _,
const Instruction* inst, uint32_t scope) {
SpvOp opcode = inst->opcode();
@@ -52,6 +69,11 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _,
return SPV_SUCCESS;
}
if (is_const_int32 && !IsValidScope(value)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
}
// Vulkan specific rules
if (spvIsVulkanEnv(_.context()->target_env)) {
// Vulkan 1.1 specific rules
@@ -154,6 +176,11 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst,
return SPV_SUCCESS;
}
if (is_const_int32 && !IsValidScope(value)) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "Invalid scope value:\n " << _.Disassemble(*_.FindDef(scope));
}
if (value == SpvScopeQueueFamilyKHR) {
if (_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
return SPV_SUCCESS;

View File

@@ -14,11 +14,10 @@
// Ensures type declarations are unique unless allowed by the specification.
#include "source/val/validate.h"
#include "source/opcode.h"
#include "source/spirv_target_env.h"
#include "source/val/instruction.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
namespace spvtools {
@@ -67,6 +66,16 @@ spv_result_t ValidateUniqueness(ValidationState_t& _, const Instruction* inst) {
return SPV_SUCCESS;
}
spv_result_t ValidateTypeInt(ValidationState_t& _, const Instruction* inst) {
const auto signedness_index = 2;
const auto signedness = inst->GetOperandAs<uint32_t>(signedness_index);
if (signedness != 0 && signedness != 1) {
return _.diag(SPV_ERROR_INVALID_VALUE, inst)
<< "OpTypeInt has invalid signedness:";
}
return SPV_SUCCESS;
}
spv_result_t ValidateTypeVector(ValidationState_t& _, const Instruction* inst) {
const auto component_index = 1;
const auto component_id = inst->GetOperandAs<uint32_t>(component_index);
@@ -310,6 +319,7 @@ spv_result_t ValidateTypePointer(ValidationState_t& _,
}
return SPV_SUCCESS;
}
} // namespace
spv_result_t ValidateTypeFunction(ValidationState_t& _,
const Instruction* inst) {
@@ -428,8 +438,6 @@ spv_result_t ValidateTypeCooperativeMatrixNV(ValidationState_t& _,
return SPV_SUCCESS;
}
} // namespace
spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
if (!spvOpcodeGeneratesType(inst->opcode()) &&
inst->opcode() != SpvOpTypeForwardPointer) {
@@ -439,6 +447,9 @@ spv_result_t TypePass(ValidationState_t& _, const Instruction* inst) {
if (auto error = ValidateUniqueness(_, inst)) return error;
switch (inst->opcode()) {
case SpvOpTypeInt:
if (auto error = ValidateTypeInt(_, inst)) return error;
break;
case SpvOpTypeVector:
if (auto error = ValidateTypeVector(_, inst)) return error;
break;

View File

@@ -19,6 +19,7 @@
#include <utility>
#include "source/opcode.h"
#include "source/spirv_constant.h"
#include "source/spirv_target_env.h"
#include "source/val/basic_block.h"
#include "source/val/construct.h"
@@ -146,6 +147,30 @@ spv_result_t CountInstructions(void* user_data,
return SPV_SUCCESS;
}
spv_result_t setHeader(void* user_data, spv_endianness_t, uint32_t,
uint32_t version, uint32_t generator, uint32_t id_bound,
uint32_t) {
ValidationState_t& vstate =
*(reinterpret_cast<ValidationState_t*>(user_data));
vstate.setIdBound(id_bound);
vstate.setGenerator(generator);
vstate.setVersion(version);
return SPV_SUCCESS;
}
// Add features based on SPIR-V core version number.
void UpdateFeaturesBasedOnSpirvVersion(ValidationState_t::Feature* features,
uint32_t version) {
assert(features);
if (version >= SPV_SPIRV_VERSION_WORD(1, 4)) {
features->select_between_composites = true;
features->copy_memory_permits_two_memory_accesses = true;
features->uconvert_spec_constant_op = true;
features->nonwritable_var_in_function_or_private = true;
}
}
} // namespace
ValidationState_t::ValidationState_t(const spv_const_context ctx,
@@ -205,11 +230,12 @@ ValidationState_t::ValidationState_t(const spv_const_context ctx,
spv_context_t hijacked_context = *ctx;
hijacked_context.consumer = [](spv_message_level_t, const char*,
const spv_position_t&, const char*) {};
spvBinaryParse(&hijacked_context, this, words, num_words,
/* parsed_header = */ nullptr, CountInstructions,
spvBinaryParse(&hijacked_context, this, words, num_words, setHeader,
CountInstructions,
/* diagnostic = */ nullptr);
preallocateStorage();
}
UpdateFeaturesBasedOnSpirvVersion(&features_, version_);
friendly_mapper_ = spvtools::MakeUnique<spvtools::FriendlyNameMapper>(
context_, words_, num_words_);
@@ -447,7 +473,7 @@ void ValidationState_t::set_addressing_model(SpvAddressingModel am) {
pointer_size_and_alignment_ = 4;
break;
default:
// fall through
// fall through
case SpvAddressingModelPhysical64:
case SpvAddressingModelPhysicalStorageBuffer64EXT:
pointer_size_and_alignment_ = 8;
@@ -1109,5 +1135,77 @@ std::string ValidationState_t::Disassemble(const uint32_t* words,
words_, num_words_, disassembly_options);
}
bool ValidationState_t::LogicallyMatch(const Instruction* lhs,
const Instruction* rhs,
bool check_decorations) {
if (lhs->opcode() != rhs->opcode()) {
return false;
}
if (check_decorations) {
const auto& dec_a = id_decorations(lhs->id());
const auto& dec_b = id_decorations(rhs->id());
for (const auto& dec : dec_b) {
if (std::find(dec_a.begin(), dec_a.end(), dec) == dec_a.end()) {
return false;
}
}
}
if (lhs->opcode() == SpvOpTypeArray) {
// Size operands must match.
if (lhs->GetOperandAs<uint32_t>(2u) != rhs->GetOperandAs<uint32_t>(2u)) {
return false;
}
// Elements must match or logically match.
const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(1u);
const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(1u);
if (lhs_ele_id == rhs_ele_id) {
return true;
}
const auto lhs_ele = FindDef(lhs_ele_id);
const auto rhs_ele = FindDef(rhs_ele_id);
if (!lhs_ele || !rhs_ele) {
return false;
}
return LogicallyMatch(lhs_ele, rhs_ele, check_decorations);
} else if (lhs->opcode() == SpvOpTypeStruct) {
// Number of elements must match.
if (lhs->operands().size() != rhs->operands().size()) {
return false;
}
for (size_t i = 1u; i < lhs->operands().size(); ++i) {
const auto lhs_ele_id = lhs->GetOperandAs<uint32_t>(i);
const auto rhs_ele_id = rhs->GetOperandAs<uint32_t>(i);
// Elements must match or logically match.
if (lhs_ele_id == rhs_ele_id) {
continue;
}
const auto lhs_ele = FindDef(lhs_ele_id);
const auto rhs_ele = FindDef(rhs_ele_id);
if (!lhs_ele || !rhs_ele) {
return false;
}
if (!LogicallyMatch(lhs_ele, rhs_ele, check_decorations)) {
return false;
}
}
// All checks passed.
return true;
}
// No other opcodes are acceptable at this point. Arrays and structs are
// caught above and if they're elements are not arrays or structs they are
// required to match exactly.
return false;
}
} // namespace val
} // namespace spvtools

View File

@@ -106,9 +106,20 @@ class ValidationState_t {
// Members need not be listed in offset order
bool scalar_block_layout = false;
// Permit UConvert as an OpSpecConstantOp operation.
// SPIR-V 1.4 allows us to select between any two composite values
// of the same type.
bool select_between_composites = false;
// SPIR-V 1.4 allows two memory access operands for OpCopyMemory and
// OpCopyMemorySized.
bool copy_memory_permits_two_memory_accesses = false;
// SPIR-V 1.4 allows UConvert as a spec constant op in any environment.
// The Kernel capability already enables it, separately from this flag.
bool uconvert_spec_constant_op = false;
// SPIR-V 1.4 allows Function and Private variables to be NonWritable
bool nonwritable_var_in_function_or_private = false;
};
ValidationState_t(const spv_const_context context,
@@ -653,6 +664,21 @@ class ValidationState_t {
spv_result_t CooperativeMatrixShapesMatch(const Instruction* inst,
uint32_t m1, uint32_t m2);
// Returns true if |lhs| and |rhs| logically match and, if the decorations of
// |rhs| are a subset of |lhs|.
//
// 1. Must both be either OpTypeArray or OpTypeStruct
// 2. If OpTypeArray, then
// * Length must be the same
// * Element type must match or logically match
// 3. If OpTypeStruct, then
// * Both have same number of elements
// * Element N for both structs must match or logically match
//
// If |check_decorations| is false, then the decorations are not checked.
bool LogicallyMatch(const Instruction* lhs, const Instruction* rhs,
bool check_decorations);
private:
ValidationState_t(const ValidationState_t&);

View File

@@ -129,6 +129,7 @@ set(TEST_SOURCES
text_start_new_inst_test.cpp
text_to_binary.annotation_test.cpp
text_to_binary.barrier_test.cpp
text_to_binary.composite_test.cpp
text_to_binary.constant_test.cpp
text_to_binary.control_flow_test.cpp
text_to_binary_test.cpp

View File

@@ -51,7 +51,7 @@ TEST_F(BinaryHeaderGet, Default) {
ASSERT_EQ(SPV_SUCCESS, spvBinaryHeaderGet(&const_bin, endian, &header));
ASSERT_EQ(static_cast<uint32_t>(SpvMagicNumber), header.magic);
ASSERT_EQ(0x00010300u, header.version);
ASSERT_EQ(0x00010400u, header.version);
ASSERT_EQ(static_cast<uint32_t>(SPV_GENERATOR_CODEPLAY), header.generator);
ASSERT_EQ(1u, header.bound);
ASSERT_EQ(0u, header.schema);

View File

@@ -81,6 +81,7 @@ add_spvtools_unittest(TARGET opt
scalar_replacement_test.cpp
set_spec_const_default_value_test.cpp
simplification_test.cpp
split_invalid_unreachable_test.cpp
strength_reduction_test.cpp
strip_atomic_counter_memory_test.cpp
strip_debug_info_test.cpp

View File

@@ -6389,6 +6389,113 @@ OpFunctionEnd
SinglePassRunAndCheck<AggressiveDCEPass>(before, before, true, true);
}
TEST_F(AggressiveDCETest, DeadInputInterfaceV13) {
const std::string spirv = R"(
; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
; CHECK: [[var]] = OpVariable
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %dead
OpExecutionMode %main LocalSize 1 1 1
OpName %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_input_int = OpTypePointer Input %int
%dead = OpVariable %ptr_input_int Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_3);
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
}
TEST_F(AggressiveDCETest, DeadInputInterfaceV14) {
const std::string spirv = R"(
; CHECK: OpEntryPoint GLCompute %main "main" [[var:%\w+]]
; CHECK: [[var]] = OpVariable
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %dead
OpExecutionMode %main LocalSize 1 1 1
OpName %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_input_int = OpTypePointer Input %int
%dead = OpVariable %ptr_input_int Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
}
TEST_F(AggressiveDCETest, DeadInterfaceV14) {
const std::string spirv = R"(
; CHECK-NOT: OpEntryPoint GLCompute %main "main" %
; CHECK: OpEntryPoint GLCompute %main "main"
; CHECK-NOT: OpVariable
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %dead
OpExecutionMode %main LocalSize 1 1 1
OpName %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_private_int = OpTypePointer Private %int
%dead = OpVariable %ptr_private_int Private
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
}
TEST_F(AggressiveDCETest, DeadInterfacesV14) {
const std::string spirv = R"(
; CHECK: OpEntryPoint GLCompute %main "main" %live1 %live2
; CHECK-NOT: %dead
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %live1 %dead1 %dead2 %live2
OpExecutionMode %main LocalSize 1 1 1
OpName %main "main"
OpName %live1 "live1"
OpName %live2 "live2"
OpName %dead1 "dead1"
OpName %dead2 "dead2"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int0 = OpConstant %int 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%live1 = OpVariable %ptr_ssbo_int StorageBuffer
%live2 = OpVariable %ptr_ssbo_int StorageBuffer
%dead1 = OpVariable %ptr_ssbo_int StorageBuffer
%dead2 = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpStore %live1 %int0
OpStore %live2 %int0
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<AggressiveDCEPass>(spirv, true);
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// Check that logical addressing required

View File

@@ -709,8 +709,8 @@ OpDecorate %1 Aliased
EXPECT_THAT(GetErrorMessage(), "");
std::string expected_decorations =
R"(OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
OpDecorateId %5 HlslCounterBufferGOOGLE %2
R"(OpDecorateString %5 UserSemantic "blah"
OpDecorateId %5 CounterBuffer %2
OpDecorate %5 Aliased
)";
EXPECT_THAT(ToText(decorations), expected_decorations);
@@ -720,11 +720,11 @@ OpCapability Linkage
OpExtension "SPV_GOOGLE_hlsl_functionality1"
OpExtension "SPV_GOOGLE_decorate_string"
OpMemoryModel Logical GLSL450
OpDecorateStringGOOGLE %1 HlslSemanticGOOGLE "blah"
OpDecorateId %1 HlslCounterBufferGOOGLE %2
OpDecorateString %1 UserSemantic "blah"
OpDecorateId %1 CounterBuffer %2
OpDecorate %1 Aliased
OpDecorateStringGOOGLE %5 HlslSemanticGOOGLE "blah"
OpDecorateId %5 HlslCounterBufferGOOGLE %2
OpDecorateString %5 UserSemantic "blah"
OpDecorateId %5 CounterBuffer %2
OpDecorate %5 Aliased
%3 = OpTypeInt 32 0
%4 = OpTypePointer Uniform %3

View File

@@ -6311,6 +6311,181 @@ INSTANTIATE_TEST_SUITE_P(OpEntryPointFoldingTest, EntryPointFoldingTest,
9, true)
));
using SPV14FoldingTest =
::testing::TestWithParam<InstructionFoldingCase<bool>>;
TEST_P(SPV14FoldingTest, Case) {
const auto& tc = GetParam();
// Build module.
std::unique_ptr<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_4, nullptr, tc.test_body,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Fold the instruction to test.
analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
EXPECT_EQ(succeeded, tc.expected_result);
if (succeeded) {
Match(tc.test_body, context.get());
}
}
INSTANTIATE_TEST_SUITE_P(SPV14FoldingTest, SPV14FoldingTest,
::testing::Values(
// Test case 0: select vectors with scalar condition.
InstructionFoldingCase<bool>(std::string() +
"; CHECK-NOT: OpSelect\n" +
"; CHECK: %3 = OpCopyObject {{%\\w+}} %1\n" +
"OpCapability Shader\n" +
"OpCapability Linkage\n" +
"%void = OpTypeVoid\n" +
"%bool = OpTypeBool\n" +
"%true = OpConstantTrue %bool\n" +
"%int = OpTypeInt 32 0\n" +
"%int4 = OpTypeVector %int 4\n" +
"%int_0 = OpConstant %int 0\n" +
"%int_1 = OpConstant %int 1\n" +
"%1 = OpUndef %int4\n" +
"%2 = OpUndef %int4\n" +
"%void_fn = OpTypeFunction %void\n" +
"%func = OpFunction %void None %void_fn\n" +
"%entry = OpLabel\n" +
"%3 = OpSelect %int4 %true %1 %2\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
3, true),
// Test case 1: select struct with scalar condition.
InstructionFoldingCase<bool>(std::string() +
"; CHECK-NOT: OpSelect\n" +
"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
"OpCapability Shader\n" +
"OpCapability Linkage\n" +
"%void = OpTypeVoid\n" +
"%bool = OpTypeBool\n" +
"%true = OpConstantFalse %bool\n" +
"%int = OpTypeInt 32 0\n" +
"%struct = OpTypeStruct %int %int %int %int\n" +
"%int_0 = OpConstant %int 0\n" +
"%int_1 = OpConstant %int 1\n" +
"%1 = OpUndef %struct\n" +
"%2 = OpUndef %struct\n" +
"%void_fn = OpTypeFunction %void\n" +
"%func = OpFunction %void None %void_fn\n" +
"%entry = OpLabel\n" +
"%3 = OpSelect %struct %true %1 %2\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
3, true),
// Test case 1: select array with scalar condition.
InstructionFoldingCase<bool>(std::string() +
"; CHECK-NOT: OpSelect\n" +
"; CHECK: %3 = OpCopyObject {{%\\w+}} %2\n" +
"OpCapability Shader\n" +
"OpCapability Linkage\n" +
"%void = OpTypeVoid\n" +
"%bool = OpTypeBool\n" +
"%true = OpConstantFalse %bool\n" +
"%int = OpTypeInt 32 0\n" +
"%int_0 = OpConstant %int 0\n" +
"%int_1 = OpConstant %int 1\n" +
"%int_4 = OpConstant %int 4\n" +
"%array = OpTypeStruct %int %int %int %int\n" +
"%1 = OpUndef %array\n" +
"%2 = OpUndef %array\n" +
"%void_fn = OpTypeFunction %void\n" +
"%func = OpFunction %void None %void_fn\n" +
"%entry = OpLabel\n" +
"%3 = OpSelect %array %true %1 %2\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
3, true)
));
std::string FloatControlsHeader(const std::string& capabilities) {
std::string header = R"(
OpCapability Shader
)" + capabilities + R"(
%void = OpTypeVoid
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%float_1 = OpConstant %float 1
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
)";
return header;
}
using FloatControlsFoldingTest =
::testing::TestWithParam<InstructionFoldingCase<bool>>;
TEST_P(FloatControlsFoldingTest, Case) {
const auto& tc = GetParam();
// Build module.
std::unique_ptr<IRContext> context =
BuildModule(SPV_ENV_UNIVERSAL_1_4, nullptr, tc.test_body,
SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
ASSERT_NE(nullptr, context);
// Fold the instruction to test.
analysis::DefUseManager* def_use_mgr = context->get_def_use_mgr();
Instruction* inst = def_use_mgr->GetDef(tc.id_to_fold);
std::unique_ptr<Instruction> original_inst(inst->Clone(context.get()));
bool succeeded = context->get_instruction_folder().FoldInstruction(inst);
EXPECT_EQ(succeeded, tc.expected_result);
if (succeeded) {
Match(tc.test_body, context.get());
}
}
INSTANTIATE_TEST_SUITE_P(FloatControlsFoldingTest, FloatControlsFoldingTest,
::testing::Values(
// Test case 0: no folding with DenormPreserve
InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability DenormPreserve") +
"%1 = OpFAdd %float %float_0 %float_1\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
1, false),
// Test case 1: no folding with DenormFlushToZero
InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability DenormFlushToZero") +
"%1 = OpFAdd %float %float_0 %float_1\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
1, false),
// Test case 2: no folding with SignedZeroInfNanPreserve
InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability SignedZeroInfNanPreserve") +
"%1 = OpFAdd %float %float_0 %float_1\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
1, false),
// Test case 3: no folding with RoundingModeRTE
InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability RoundingModeRTE") +
"%1 = OpFAdd %float %float_0 %float_1\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
1, false),
// Test case 4: no folding with RoundingModeRTZ
InstructionFoldingCase<bool>(FloatControlsHeader("OpCapability RoundingModeRTZ") +
"%1 = OpFAdd %float %float_0 %float_1\n" +
"OpReturn\n" +
"OpFunctionEnd\n"
,
1, false)
));
} // namespace
} // namespace opt
} // namespace spvtools

File diff suppressed because it is too large Load Diff

View File

@@ -238,7 +238,8 @@ TEST(Optimizer, VulkanToWebGPUSetsCorrectPasses) {
"strip-debug",
"strip-atomic-counter-memory",
"generate-webgpu-initializers",
"legalize-vector-shuffle"};
"legalize-vector-shuffle",
"split-invalid-unreachable"};
std::sort(registered_passes.begin(), registered_passes.end());
std::sort(expected_passes.begin(), expected_passes.end());
@@ -524,7 +525,74 @@ INSTANTIATE_TEST_SUITE_P(
"OpReturn\n"
"OpFunctionEnd\n",
// pass
"legalize-vector-shuffle"}}));
"legalize-vector-shuffle"},
// Split Invalid Unreachable
{// input
"OpCapability Shader\n"
"OpCapability VulkanMemoryModelKHR\n"
"OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
"OpMemoryModel Logical VulkanKHR\n"
"OpEntryPoint Vertex %1 \"shader\"\n"
"%uint = OpTypeInt 32 0\n"
"%uint_1 = OpConstant %uint 1\n"
"%uint_2 = OpConstant %uint 2\n"
"%void = OpTypeVoid\n"
"%bool = OpTypeBool\n"
"%7 = OpTypeFunction %void\n"
"%1 = OpFunction %void None %7\n"
"%8 = OpLabel\n"
"OpBranch %9\n"
"%9 = OpLabel\n"
"OpLoopMerge %10 %11 None\n"
"OpBranch %12\n"
"%12 = OpLabel\n"
"%13 = OpSLessThan %bool %uint_1 %uint_2\n"
"OpSelectionMerge %11 None\n"
"OpBranchConditional %13 %14 %15\n"
"%14 = OpLabel\n"
"OpReturn\n"
"%15 = OpLabel\n"
"OpReturn\n"
"%10 = OpLabel\n"
"OpUnreachable\n"
"%11 = OpLabel\n"
"OpBranch %9\n"
"OpFunctionEnd\n",
// expected
"OpCapability Shader\n"
"OpCapability VulkanMemoryModelKHR\n"
"OpExtension \"SPV_KHR_vulkan_memory_model\"\n"
"OpMemoryModel Logical VulkanKHR\n"
"OpEntryPoint Vertex %1 \"shader\"\n"
"%uint = OpTypeInt 32 0\n"
"%uint_1 = OpConstant %uint 1\n"
"%uint_2 = OpConstant %uint 2\n"
"%void = OpTypeVoid\n"
"%bool = OpTypeBool\n"
"%7 = OpTypeFunction %void\n"
"%1 = OpFunction %void None %7\n"
"%8 = OpLabel\n"
"OpBranch %9\n"
"%9 = OpLabel\n"
"OpLoopMerge %10 %11 None\n"
"OpBranch %12\n"
"%12 = OpLabel\n"
"%13 = OpSLessThan %bool %uint_1 %uint_2\n"
"OpSelectionMerge %16 None\n"
"OpBranchConditional %13 %14 %15\n"
"%14 = OpLabel\n"
"OpReturn\n"
"%15 = OpLabel\n"
"OpReturn\n"
"%10 = OpLabel\n"
"OpUnreachable\n"
"%16 = OpLabel\n"
"OpUnreachable\n"
"%11 = OpLabel\n"
"OpBranch %9\n"
"OpFunctionEnd\n",
// pass
"split-invalid-unreachable"}}));
TEST(Optimizer, WebGPUToVulkanSetsCorrectPasses) {
Optimizer opt(SPV_ENV_VULKAN_1_1);

View File

@@ -49,18 +49,18 @@ class PassTest : public TestT {
[](spv_message_level_t, const char*, const spv_position_t&,
const char* message) { std::cerr << message << std::endl; }),
context_(nullptr),
tools_(SPV_ENV_UNIVERSAL_1_3),
manager_(new PassManager()),
assemble_options_(SpirvTools::kDefaultAssembleOption),
disassemble_options_(SpirvTools::kDefaultDisassembleOption) {}
disassemble_options_(SpirvTools::kDefaultDisassembleOption),
env_(SPV_ENV_UNIVERSAL_1_3) {}
// Runs the given |pass| on the binary assembled from the |original|.
// Returns a tuple of the optimized binary and the boolean value returned
// from pass Process() function.
std::tuple<std::vector<uint32_t>, Pass::Status> OptimizeToBinary(
Pass* pass, const std::string& original, bool skip_nop) {
context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, consumer_, original,
assemble_options_));
context_ =
std::move(BuildModule(env_, consumer_, original, assemble_options_));
EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n"
<< original << std::endl;
if (!context()) {
@@ -97,8 +97,7 @@ class PassTest : public TestT {
std::tie(optimized_bin, status) = SinglePassRunToBinary<PassT>(
assembly, skip_nop, std::forward<Args>(args)...);
if (do_validation) {
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
spv_context spvContext = spvContextCreate(target_env);
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(
@@ -109,8 +108,9 @@ class PassTest : public TestT {
spvContextDestroy(spvContext);
}
std::string optimized_asm;
SpirvTools tools(env_);
EXPECT_TRUE(
tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
<< "Disassembling failed for shader:\n"
<< assembly << std::endl;
return std::make_tuple(optimized_asm, status);
@@ -134,8 +134,7 @@ class PassTest : public TestT {
EXPECT_EQ(original == expected,
status == Pass::Status::SuccessWithoutChange);
if (do_validation) {
spv_target_env target_env = SPV_ENV_UNIVERSAL_1_3;
spv_context spvContext = spvContextCreate(target_env);
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(
@@ -146,8 +145,9 @@ class PassTest : public TestT {
spvContextDestroy(spvContext);
}
std::string optimized_asm;
SpirvTools tools(env_);
EXPECT_TRUE(
tools_.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
tools.Disassemble(optimized_bin, &optimized_asm, disassemble_options_))
<< "Disassembling failed for shader:\n"
<< original << std::endl;
EXPECT_EQ(expected, optimized_asm);
@@ -202,8 +202,8 @@ class PassTest : public TestT {
void RunAndCheck(const std::string& original, const std::string& expected) {
assert(manager_->NumPasses());
context_ = std::move(BuildModule(SPV_ENV_UNIVERSAL_1_3, nullptr, original,
assemble_options_));
context_ =
std::move(BuildModule(env_, nullptr, original, assemble_options_));
ASSERT_NE(nullptr, context());
manager_->Run(context());
@@ -212,7 +212,8 @@ class PassTest : public TestT {
context()->module()->ToBinary(&binary, /* skip_nop = */ false);
std::string optimized;
EXPECT_TRUE(tools_.Disassemble(binary, &optimized, disassemble_options_));
SpirvTools tools(env_);
EXPECT_TRUE(tools.Disassemble(binary, &optimized, disassemble_options_));
EXPECT_EQ(expected, optimized);
}
@@ -233,14 +234,16 @@ class PassTest : public TestT {
spv_validator_options ValidatorOptions() { return &validator_options_; }
void SetTargetEnv(spv_target_env env) { env_ = env; }
private:
MessageConsumer consumer_; // Message consumer.
std::unique_ptr<IRContext> context_; // IR context
SpirvTools tools_; // An instance for calling SPIRV-Tools functionalities.
MessageConsumer consumer_; // Message consumer.
std::unique_ptr<IRContext> context_; // IR context
std::unique_ptr<PassManager> manager_; // The pass manager.
uint32_t assemble_options_;
uint32_t disassemble_options_;
spv_validator_options_t validator_options_;
spv_target_env env_;
};
} // namespace opt

View File

@@ -308,6 +308,117 @@ TEST_F(PrivateToLocalTest, CreatePointerToAmbiguousStruct2) {
SinglePassRunAndMatch<PrivateToLocalPass>(text, false);
}
TEST_F(PrivateToLocalTest, SPV14RemoveFromInterface) {
const std::string text = R"(
; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
; CHECK: OpEntryPoint GLCompute %foo "foo" %in
; CHECK: %priv = OpVariable {{%\w+}} Function
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %foo "foo" %in %priv
OpExecutionMode %foo LocalSize 1 1 1
OpName %foo "foo"
OpName %in "in"
OpName %priv "priv"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr_private_int = OpTypePointer Private %int
%in = OpVariable %ptr_ssbo_int StorageBuffer
%priv = OpVariable %ptr_private_int Private
%void_fn = OpTypeFunction %void
%foo = OpFunction %void None %void_fn
%entry = OpLabel
%ld = OpLoad %int %in
OpStore %priv %ld
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
}
TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleEntryPoints) {
const std::string text = R"(
; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv
; CHECK-NOT: OpEntryPoint GLCompute %foo "bar" %in %priv
; CHECK: OpEntryPoint GLCompute %foo "foo" %in
; CHECK: OpEntryPoint GLCompute %foo "bar" %in
; CHECK: %priv = OpVariable {{%\w+}} Function
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %foo "foo" %in %priv
OpEntryPoint GLCompute %foo "bar" %in %priv
OpExecutionMode %foo LocalSize 1 1 1
OpName %foo "foo"
OpName %in "in"
OpName %priv "priv"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr_private_int = OpTypePointer Private %int
%in = OpVariable %ptr_ssbo_int StorageBuffer
%priv = OpVariable %ptr_private_int Private
%void_fn = OpTypeFunction %void
%foo = OpFunction %void None %void_fn
%entry = OpLabel
%ld = OpLoad %int %in
OpStore %priv %ld
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
}
TEST_F(PrivateToLocalTest, SPV14RemoveFromInterfaceMultipleVariables) {
const std::string text = R"(
; CHECK-NOT: OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
; CHECK: OpEntryPoint GLCompute %foo "foo" %in
; CHECK: %priv1 = OpVariable {{%\w+}} Function
; CHECK: %priv2 = OpVariable {{%\w+}} Function
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %foo "foo" %in %priv1 %priv2
OpExecutionMode %foo LocalSize 1 1 1
OpName %foo "foo"
OpName %in "in"
OpName %priv1 "priv1"
OpName %priv2 "priv2"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr_private_int = OpTypePointer Private %int
%in = OpVariable %ptr_ssbo_int StorageBuffer
%priv1 = OpVariable %ptr_private_int Private
%priv2 = OpVariable %ptr_private_int Private
%void_fn = OpTypeFunction %void
%foo = OpFunction %void None %void_fn
%entry = OpLabel
%1 = OpFunctionCall %void %bar1
%2 = OpFunctionCall %void %bar2
OpReturn
OpFunctionEnd
%bar1 = OpFunction %void None %void_fn
%3 = OpLabel
%ld1 = OpLoad %int %in
OpStore %priv1 %ld1
OpReturn
OpFunctionEnd
%bar2 = OpFunction %void None %void_fn
%4 = OpLabel
%ld2 = OpLoad %int %in
OpStore %priv2 %ld2
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<PrivateToLocalPass>(text, true);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -1620,6 +1620,43 @@ TEST_F(ScalarReplacementTest, TestAccessChainWithNoIndexes) {
EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result));
}
// Test that id overflow is handled gracefully.
TEST_F(ScalarReplacementTest, IdBoundOverflow) {
const std::string text = R"(
OpCapability ImageQuery
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpDecorate %4194302 DescriptorSet 1073495039
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeFloat 32
%7 = OpTypeStruct %6 %6
%557056 = OpTypeStruct %7
%9 = OpTypePointer Function %7
%18 = OpTypeFunction %7 %9
%4 = OpFunction %2 Pure|Const %3
%1836763 = OpLabel
%4194302 = OpVariable %9 Function
%10 = OpVariable %9 Function
OpKill
%4194301 = OpLabel
%524296 = OpLoad %7 %4194302
OpKill
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
std::vector<Message> messages = {
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."},
{SPV_MSG_ERROR, "", 0, 0, "ID overflow. Try running compact-ids."}};
SetMessageConsumer(GetTestMessageConsumer(messages));
auto result =
SinglePassRunAndDisassemble<ScalarReplacementPass>(text, true, false);
EXPECT_EQ(Pass::Status::Failure, std::get<1>(result));
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -0,0 +1,155 @@
// Copyright (c) 2019 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 <vector>
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"
namespace spvtools {
namespace opt {
namespace {
using SplitInvalidUnreachableTest = PassTest<::testing::Test>;
std::string spirv_header = R"(OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Vertex %1 "shader"
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%void = OpTypeVoid
%bool = OpTypeBool
%7 = OpTypeFunction %void
)";
std::string function_head = R"(%1 = OpFunction %void None %7
%8 = OpLabel
OpBranch %9
)";
std::string function_tail = "OpFunctionEnd\n";
std::string GetLoopMergeBlock(std::string block_id, std::string merge_id,
std::string continue_id, std::string body_id) {
std::string result;
result += block_id + " = OpLabel\n";
result += "OpLoopMerge " + merge_id + " " + continue_id + " None\n";
result += "OpBranch " + body_id + "\n";
return result;
}
std::string GetSelectionMergeBlock(std::string block_id,
std::string condition_id,
std::string merge_id, std::string true_id,
std::string false_id) {
std::string result;
result += block_id + " = OpLabel\n";
result += condition_id + " = OpSLessThan %bool %uint_1 %uint_2\n";
result += "OpSelectionMerge " + merge_id + " None\n";
result += "OpBranchConditional " + condition_id + " " + true_id + " " +
false_id + "\n";
return result;
}
std::string GetReturnBlock(std::string block_id) {
std::string result;
result += block_id + " = OpLabel\n";
result += "OpReturn\n";
return result;
}
std::string GetUnreachableBlock(std::string block_id) {
std::string result;
result += block_id + " = OpLabel\n";
result += "OpUnreachable\n";
return result;
}
std::string GetBranchBlock(std::string block_id, std::string target_id) {
std::string result;
result += block_id + " = OpLabel\n";
result += "OpBranch " + target_id + "\n";
return result;
}
TEST_F(SplitInvalidUnreachableTest, NoInvalidBlocks) {
std::string input = spirv_header + function_head;
input += GetLoopMergeBlock("%9", "%10", "%11", "%12");
input += GetSelectionMergeBlock("%12", "%13", "%14", "%15", "%16");
input += GetReturnBlock("%15");
input += GetReturnBlock("%16");
input += GetUnreachableBlock("%10");
input += GetBranchBlock("%11", "%9");
input += GetUnreachableBlock("%14");
input += function_tail;
SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, input,
/* skip_nop = */ false);
}
TEST_F(SplitInvalidUnreachableTest, SelectionInLoop) {
std::string input = spirv_header + function_head;
input += GetLoopMergeBlock("%9", "%10", "%11", "%12");
input += GetSelectionMergeBlock("%12", "%13", "%11", "%15", "%16");
input += GetReturnBlock("%15");
input += GetReturnBlock("%16");
input += GetUnreachableBlock("%10");
input += GetBranchBlock("%11", "%9");
input += function_tail;
std::string expected = spirv_header + function_head;
expected += GetLoopMergeBlock("%9", "%10", "%11", "%12");
expected += GetSelectionMergeBlock("%12", "%13", "%16", "%14", "%15");
expected += GetReturnBlock("%14");
expected += GetReturnBlock("%15");
expected += GetUnreachableBlock("%10");
expected += GetUnreachableBlock("%16");
expected += GetBranchBlock("%11", "%9");
expected += function_tail;
SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, expected,
/* skip_nop = */ false);
}
TEST_F(SplitInvalidUnreachableTest, LoopInSelection) {
std::string input = spirv_header + function_head;
input += GetSelectionMergeBlock("%9", "%10", "%11", "%12", "%13");
input += GetLoopMergeBlock("%12", "%14", "%11", "%15");
input += GetReturnBlock("%13");
input += GetUnreachableBlock("%14");
input += GetBranchBlock("%11", "%12");
input += GetReturnBlock("%15");
input += function_tail;
std::string expected = spirv_header + function_head;
expected += GetSelectionMergeBlock("%9", "%10", "%16", "%12", "%13");
expected += GetLoopMergeBlock("%12", "%14", "%11", "%15");
expected += GetReturnBlock("%13");
expected += GetUnreachableBlock("%14");
expected += GetUnreachableBlock("%16");
expected += GetBranchBlock("%11", "%12");
expected += GetReturnBlock("%15");
expected += function_tail;
SinglePassRunAndCheck<SplitInvalidUnreachablePass>(input, expected,
/* skip_nop = */ false);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@@ -1713,4 +1713,328 @@ OpFunctionEnd
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryAddOperands) {
const std::string text = R"(
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} None None
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryDuplicateOperand) {
const std::string text = R"(
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Nontemporal Nontemporal
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Nontemporal
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14NormalizeCopyMemoryDuplicateOperands) {
const std::string text = R"(
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned 4 Aligned 4
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherent) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]] None
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %dst Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherentPreviousArgs) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[scope]] Aligned 4
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %dst Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemorySrcCoherent) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} None MakePointerVisibleKHR|NonPrivatePointerKHR [[scope]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %src Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemorySrcCoherentPreviousArgs) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned 4 Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 [[scope]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %src Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothCoherent) {
const std::string text = R"(
; CHECK-DAG: [[queue:%\w+]] = OpConstant {{%\w+}} 5
; CHECK-DAG: [[wg:%\w+]] = OpConstant {{%\w+}} 2
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[wg]] MakePointerVisibleKHR|NonPrivatePointerKHR [[queue]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %src Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr_wg_int = OpTypePointer Workgroup %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_wg_int Workgroup
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothCoherentPreviousArgs) {
const std::string text = R"(
; CHECK-DAG: [[queue:%\w+]] = OpConstant {{%\w+}} 5
; CHECK-DAG: [[wg:%\w+]] = OpConstant {{%\w+}} 2
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[queue]] Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 [[wg]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %dst Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr_wg_int = OpTypePointer Workgroup %int
%src = OpVariable %ptr_wg_int Workgroup
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothVolatile) {
const std::string text = R"(
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Volatile Volatile
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %src Volatile
OpDecorate %dst Volatile
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryBothVolatilePreviousArgs) {
const std::string text = R"(
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Volatile|Aligned 4 Volatile|Aligned 4
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %src Volatile
OpDecorate %dst Volatile
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest, SPV14CopyMemoryDstCoherentTwoOperands) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} MakePointerAvailableKHR|NonPrivatePointerKHR [[scope]] None
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %dst Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src None None
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
TEST_F(UpgradeMemoryModelTest,
SPV14CopyMemoryDstCoherentPreviousArgsTwoOperands) {
const std::string text = R"(
; CHECK: [[scope:%\w+]] = OpConstant {{%\w+}} 5
; CHECK: OpCopyMemory {{%\w+}} {{%\w+}} Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 [[scope]] Aligned 8
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %func "func" %src %dst
OpDecorate %dst Coherent
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%src = OpVariable %ptr_ssbo_int StorageBuffer
%dst = OpVariable %ptr_ssbo_int StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %dst %src Aligned 4 Aligned 8
OpReturn
OpFunctionEnd
)";
SetTargetEnv(SPV_ENV_UNIVERSAL_1_4);
SinglePassRunAndMatch<opt::UpgradeMemoryModel>(text, true);
}
} // namespace

View File

@@ -60,6 +60,29 @@ TEST_P(OpDecorateSimpleTest, AnySimpleDecoration) {
Eq(input.str()));
}
// Like above, but parameters to the decoration are IDs.
using OpDecorateSimpleIdTest =
spvtest::TextToBinaryTestBase<::testing::TestWithParam<
std::tuple<spv_target_env, EnumCase<SpvDecoration>>>>;
TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) {
// This string should assemble, but should not validate.
std::stringstream input;
input << "OpDecorateId %1 " << std::get<1>(GetParam()).name();
for (auto operand : std::get<1>(GetParam()).operands())
input << " %" << operand;
input << std::endl;
EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
Eq(MakeInstruction(SpvOpDecorateId,
{1, uint32_t(std::get<1>(GetParam()).value())},
std::get<1>(GetParam()).operands())));
// Also check disassembly.
EXPECT_THAT(
EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
std::get<0>(GetParam())),
Eq(input.str()));
}
#define CASE(NAME) SpvDecoration##NAME, #NAME
INSTANTIATE_TEST_SUITE_P(
TextToBinaryDecorateSimple, OpDecorateSimpleTest,
@@ -112,6 +135,21 @@ INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
Combine(Values(SPV_ENV_UNIVERSAL_1_1),
Values(EnumCase<SpvDecoration>{
CASE(MaxByteOffset), {128}})));
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV14, OpDecorateSimpleTest,
Combine(Values(SPV_ENV_UNIVERSAL_1_4),
ValuesIn(std::vector<EnumCase<SpvDecoration>>{
{CASE(Uniform), {}},
})));
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleIdV14,
OpDecorateSimpleIdTest,
Combine(Values(SPV_ENV_UNIVERSAL_1_4),
ValuesIn(std::vector<EnumCase<SpvDecoration>>{
// In 1.4, UniformId decoration takes a
// scope Id.
{CASE(UniformId), {1}},
})));
#undef CASE
TEST_F(OpDecorateSimpleTest, WrongDecoration) {

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2018 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.
// Assembler tests for instructions in the "Group Instrucions" section of the
// SPIR-V spec.
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
using ::testing::Eq;
using ::testing::HasSubstr;
namespace spvtools {
namespace {
using spvtest::Concatenate;
using CompositeRoundTripTest = RoundTripTest;
TEST_F(CompositeRoundTripTest, Good) {
std::string spirv = "%2 = OpCopyLogical %1 %3\n";
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(CompositeRoundTripTest, V13Bad) {
std::string spirv = "%2 = OpCopyLogical %1 %3\n";
std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpCopyLogical'"));
}
} // namespace
} // namespace spvtools

View File

@@ -276,11 +276,11 @@ INSTANTIATE_TEST_SUITE_P(
{"OpCapability StorageBuffer16BitAccess\n",
MakeInstruction(SpvOpCapability,
{SpvCapabilityStorageBuffer16BitAccess})},
{"OpCapability StorageUniform16\n",
{"OpCapability UniformAndStorageBuffer16BitAccess\n",
MakeInstruction(
SpvOpCapability,
{SpvCapabilityUniformAndStorageBuffer16BitAccess})},
{"OpCapability StorageUniform16\n",
{"OpCapability UniformAndStorageBuffer16BitAccess\n",
MakeInstruction(SpvOpCapability,
{SpvCapabilityStorageUniform16})},
{"OpCapability StoragePushConstant16\n",
@@ -656,8 +656,38 @@ INSTANTIATE_TEST_SUITE_P(
// SPV_GOOGLE_decorate_string
// Now that OpDecorateString is the preferred spelling for
// OpDecorateStringGOOGLE use that name in round trip tests, and the GOOGLE
// name in an assembly-only test.
INSTANTIATE_TEST_SUITE_P(
SPV_GOOGLE_decorate_string, ExtensionRoundTripTest,
Combine(
// We'll get coverage over operand tables by trying the universal
// environments, and at least one specific environment.
Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
ValuesIn(std::vector<AssemblyCase>{
{"OpDecorateString %1 UserSemantic \"ABC\"\n",
MakeInstruction(SpvOpDecorateStringGOOGLE,
{1, SpvDecorationHlslSemanticGOOGLE},
MakeVector("ABC"))},
{"OpDecorateString %1 UserSemantic \"ABC\"\n",
MakeInstruction(SpvOpDecorateString,
{1, SpvDecorationUserSemantic},
MakeVector("ABC"))},
{"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
MakeInstruction(SpvOpMemberDecorateStringGOOGLE,
{1, 3, SpvDecorationUserSemantic},
MakeVector("DEF"))},
{"OpMemberDecorateString %1 3 UserSemantic \"DEF\"\n",
MakeInstruction(SpvOpMemberDecorateString,
{1, 3, SpvDecorationUserSemantic},
MakeVector("DEF"))},
})));
INSTANTIATE_TEST_SUITE_P(
SPV_GOOGLE_decorate_string, ExtensionAssemblyTest,
Combine(
// We'll get coverage over operand tables by trying the universal
// environments, and at least one specific environment.
@@ -676,8 +706,29 @@ INSTANTIATE_TEST_SUITE_P(
// SPV_GOOGLE_hlsl_functionality1
// Now that CounterBuffer is the preferred spelling for HlslCounterBufferGOOGLE,
// use that name in round trip tests, and the GOOGLE name in an assembly-only
// test.
INSTANTIATE_TEST_SUITE_P(
SPV_GOOGLE_hlsl_functionality1, ExtensionRoundTripTest,
Combine(
// We'll get coverage over operand tables by trying the universal
// environments, and at least one specific environment.
Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1,
SPV_ENV_UNIVERSAL_1_2, SPV_ENV_VULKAN_1_0),
// HlslSemanticGOOGLE is tested in SPV_GOOGLE_decorate_string, since
// they are coupled together.
ValuesIn(std::vector<AssemblyCase>{
{"OpDecorateId %1 CounterBuffer %2\n",
MakeInstruction(SpvOpDecorateId,
{1, SpvDecorationHlslCounterBufferGOOGLE, 2})},
{"OpDecorateId %1 CounterBuffer %2\n",
MakeInstruction(SpvOpDecorateId,
{1, SpvDecorationCounterBuffer, 2})},
})));
INSTANTIATE_TEST_SUITE_P(
SPV_GOOGLE_hlsl_functionality1, ExtensionAssemblyTest,
Combine(
// We'll get coverage over operand tables by trying the universal
// environments, and at least one specific environment.

View File

@@ -30,6 +30,7 @@ using spvtest::EnumCase;
using spvtest::MakeInstruction;
using spvtest::TextToBinaryTest;
using ::testing::Eq;
using ::testing::HasSubstr;
// Test assembly of Memory Access masks
@@ -95,12 +96,326 @@ INSTANTIATE_TEST_SUITE_P(
#undef CASE
// clang-format on
using MemoryRoundTripTest = RoundTripTest;
// OpPtrEqual appeared in SPIR-V 1.4
TEST_F(MemoryRoundTripTest, OpPtrEqualGood) {
std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
Eq(MakeInstruction(SpvOpPtrEqual, {1, 2, 3, 4})));
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpPtrEqualV13Bad) {
std::string spirv = "%2 = OpPtrEqual %1 %3 %4\n";
std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrEqual'"));
}
// OpPtrNotEqual appeared in SPIR-V 1.4
TEST_F(MemoryRoundTripTest, OpPtrNotEqualGood) {
std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
Eq(MakeInstruction(SpvOpPtrNotEqual, {1, 2, 3, 4})));
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpPtrNotEqualV13Bad) {
std::string spirv = "%2 = OpPtrNotEqual %1 %3 %4\n";
std::string err = CompileFailure(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_THAT(err, HasSubstr("Invalid Opcode name 'OpPtrNotEqual'"));
}
// OpPtrDiff appeared in SPIR-V 1.4
TEST_F(MemoryRoundTripTest, OpPtrDiffGood) {
std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
Eq(MakeInstruction(SpvOpPtrDiff, {1, 2, 3, 4})));
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpPtrDiffV13Good) {
// OpPtrDiff is enabled by a capability as well, so we can assemble
// it even in older SPIR-V environments. We do that so we can
// write tests.
std::string spirv = "%2 = OpPtrDiff %1 %3 %4\n";
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_4);
}
// OpCopyMemory
TEST_F(MemoryRoundTripTest, OpCopyMemoryNoMemAccessGood) {
std::string spirv = "OpCopyMemory %1 %2\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryTooFewArgsBad) {
std::string spirv = "OpCopyMemory %1\n";
std::string err = CompileFailure(spirv);
EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryTooManyArgsBad) {
std::string spirv = "OpCopyMemory %1 %2 %3\n";
std::string err = CompileFailure(spirv);
EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%3'"));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNoneGood) {
std::string spirv = "OpCopyMemory %1 %2 None\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 0})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVolatileGood) {
std::string spirv = "OpCopyMemory %1 %2 Volatile\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAligned8Good) {
std::string spirv = "OpCopyMemory %1 %2 Aligned 8\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 2, 8})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNontemporalGood) {
std::string spirv = "OpCopyMemory %1 %2 Nontemporal\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessAvGood) {
std::string spirv = "OpCopyMemory %1 %2 MakePointerAvailableKHR %3\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 8, 3})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessVisGood) {
std::string spirv = "OpCopyMemory %1 %2 MakePointerVisibleKHR %3\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 16, 3})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessNonPrivateGood) {
std::string spirv = "OpCopyMemory %1 %2 NonPrivatePointerKHR\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 32})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryAccessMixedGood) {
std::string spirv =
"OpCopyMemory %1 %2 "
"Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
"MakePointerVisibleKHR|NonPrivatePointerKHR 16 %3 %4\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 63, 16, 3, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV13Good) {
std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
// Note: This will assemble but should not validate for SPIR-V 1.3
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessV14Good) {
std::string spirv = "OpCopyMemory %1 %2 Volatile Volatile\n";
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 1, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemoryTwoAccessMixedV14Good) {
std::string spirv =
"OpCopyMemory %1 %2 Volatile|Nontemporal|"
"MakePointerVisibleKHR %3 "
"Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %4\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemory, {1, 2, 21, 3, 42, 16, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
// OpCopyMemorySized
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedNoMemAccessGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooFewArgsBad) {
std::string spirv = "OpCopyMemorySized %1 %2\n";
std::string err = CompileFailure(spirv);
EXPECT_THAT(err, HasSubstr("Expected operand, found end of stream"));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTooManyArgsBad) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 %4\n";
std::string err = CompileFailure(spirv);
EXPECT_THAT(err, HasSubstr("Invalid memory access operand '%4'"));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNoneGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 None\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 0})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVolatileGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAligned8Good) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 Aligned 8\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 2, 8})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNontemporalGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 Nontemporal\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessAvGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerAvailableKHR %4\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 8, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessVisGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 MakePointerVisibleKHR %4\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 16, 4})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessNonPrivateGood) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 NonPrivatePointerKHR\n";
EXPECT_THAT(CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 32})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedAccessMixedGood) {
std::string spirv =
"OpCopyMemorySized %1 %2 %3 "
"Volatile|Aligned|Nontemporal|MakePointerAvailableKHR|"
"MakePointerVisibleKHR|NonPrivatePointerKHR 16 %4 %5\n";
EXPECT_THAT(
CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 63, 16, 4, 5})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV13Good) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
// Note: This will assemble but should not validate for SPIR-V 1.3
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_3),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessV14Good) {
std::string spirv = "OpCopyMemorySized %1 %2 %3 Volatile Volatile\n";
EXPECT_THAT(CompiledInstructions(spirv, SPV_ENV_UNIVERSAL_1_4),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 1, 1})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(MemoryRoundTripTest, OpCopyMemorySizedTwoAccessMixedV14Good) {
std::string spirv =
"OpCopyMemorySized %1 %2 %3 Volatile|Nontemporal|"
"MakePointerVisibleKHR %4 "
"Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 16 %5\n";
EXPECT_THAT(
CompiledInstructions(spirv),
Eq(MakeInstruction(SpvOpCopyMemorySized, {1, 2, 3, 21, 4, 42, 16, 5})));
std::string disassembly =
EncodeAndDecodeSuccessfully(spirv, SPV_BINARY_TO_TEXT_OPTION_NONE);
EXPECT_THAT(disassembly, Eq(spirv));
}
// TODO(dneto): OpVariable with initializers
// TODO(dneto): OpImageTexelPointer
// TODO(dneto): OpLoad
// TODO(dneto): OpStore
// TODO(dneto): OpCopyMemory
// TODO(dneto): OpCopyMemorySized
// TODO(dneto): OpAccessChain
// TODO(dneto): OpInBoundsAccessChain
// TODO(dneto): OpPtrAccessChain

View File

@@ -26,6 +26,8 @@ import traceback
from spirv_test_framework import SpirvTest
from builtins import bytes
DEFAULT_SPIRV_VERSION = 0x010000
def convert_to_unix_line_endings(source):
"""Converts all line endings in source to be unix line endings."""
result = source.replace('\r\n', '\n').replace('\r', '\n')
@@ -172,7 +174,7 @@ class CorrectBinaryLengthAndPreamble(SpirvTest):
# profile
if version != spv_version and version != 0:
return False, 'Incorrect SPV binary: wrong version number'
return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version)
# Shaderc-over-Glslang (0x000d....) or
# SPIRV-Tools (0x0007....) generator number
if read_word(preamble, 2, little_endian) != 0x000d0007 and \
@@ -188,7 +190,9 @@ class CorrectBinaryLengthAndPreamble(SpirvTest):
class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble):
"""Provides methods for verifying preamble for a SPV object file."""
def verify_object_file_preamble(self, filename, spv_version=0x10000):
def verify_object_file_preamble(self,
filename,
spv_version=DEFAULT_SPIRV_VERSION):
"""Checks that the given SPIR-V binary file has correct preamble."""
success, message = verify_file_non_empty(filename)
@@ -257,6 +261,21 @@ class ValidObjectFile1_3(ReturnCodeIsZero, CorrectObjectFilePreamble):
return True, ''
class ValidObjectFile1_4(ReturnCodeIsZero, CorrectObjectFilePreamble):
"""Mixin class for checking that every input file generates a valid SPIR-V 1.4
object file following the object file naming rule, and there is no output on
stdout/stderr."""
def check_object_file_preamble(self, status):
for input_filename in status.input_filenames:
object_filename = get_object_filename(input_filename)
success, message = self.verify_object_file_preamble(
os.path.join(status.directory, object_filename), 0x10400)
if not success:
return False, message
return True, ''
class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
CorrectObjectFilePreamble):
"""Mixin class for checking that every input file generates a valid object

View File

@@ -34,7 +34,7 @@ def empty_main_assembly():
@inside_spirv_testsuite('SpirvOptBase')
class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_3):
class TestAssemblyFileAsOnlyParameter(expect.ValidObjectFile1_4):
"""Tests that spirv-opt accepts a SPIR-V object file."""
shader = placeholder.FileSPIRVShader(empty_main_assembly(), '.spvasm')
@@ -52,7 +52,7 @@ class TestHelpFlag(expect.ReturnCodeIsZero, expect.StdoutMatch):
@inside_spirv_testsuite('SpirvOptFlags')
class TestValidPassFlags(expect.ValidObjectFile1_3,
class TestValidPassFlags(expect.ValidObjectFile1_4,
expect.ExecutedListOfPasses):
"""Tests that spirv-opt accepts all valid optimization flags."""
@@ -129,7 +129,7 @@ class TestValidPassFlags(expect.ValidObjectFile1_3,
@inside_spirv_testsuite('SpirvOptFlags')
class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_3,
class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_4,
expect.ExecutedListOfPasses):
"""Tests that spirv-opt schedules all the passes triggered by -O."""
@@ -176,7 +176,7 @@ class TestPerformanceOptimizationPasses(expect.ValidObjectFile1_3,
@inside_spirv_testsuite('SpirvOptFlags')
class TestSizeOptimizationPasses(expect.ValidObjectFile1_3,
class TestSizeOptimizationPasses(expect.ValidObjectFile1_4,
expect.ExecutedListOfPasses):
"""Tests that spirv-opt schedules all the passes triggered by -Os."""
@@ -215,7 +215,7 @@ class TestSizeOptimizationPasses(expect.ValidObjectFile1_3,
@inside_spirv_testsuite('SpirvOptFlags')
class TestLegalizationPasses(expect.ValidObjectFile1_3,
class TestLegalizationPasses(expect.ValidObjectFile1_4,
expect.ExecutedListOfPasses):
"""Tests that spirv-opt schedules all the passes triggered by --legalize-hlsl.
"""

View File

@@ -63,6 +63,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop
val_memory_test.cpp
val_modes_test.cpp
val_non_uniform_test.cpp
val_opencl_test.cpp
val_primitives_test.cpp
${VAL_TEST_COMMON_SRCS}
LIBS ${SPIRV_TOOLS}

View File

@@ -1975,6 +1975,29 @@ TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
}
TEST_F(ValidateAtomics, CompareExchangeWeakV13ValV14Good) {
const std::string body = R"(
%val1 = OpAtomicCompareExchangeWeak %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateAtomics, CompareExchangeWeakV14Bad) {
const std::string body = R"(
%val1 = OpAtomicCompareExchangeWeak %u32 %u32_var %device %relaxed %relaxed %u32_0 %u32_0
)";
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"AtomicCompareExchangeWeak requires SPIR-V version 1.3 or earlier"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -954,8 +954,8 @@ TEST_F(ValidateBarriers, TypeAsMemoryScope) {
OpMemoryBarrier %u32 %u32_0
)";
CompileSuccessfully(GenerateKernelCode(body));
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
CompileSuccessfully(GenerateKernelCode(body), SPV_ENV_UNIVERSAL_1_1);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 5[%uint] cannot be a "
"type"));
}

View File

@@ -2265,8 +2265,8 @@ OpMemoryModel Logical Simple
EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("operand 5255 requires one of these extensions: "
"SPV_NV_viewport_array2"));
HasSubstr("operand ShaderViewportMaskNV(5255) requires one of "
"these extensions: SPV_NV_viewport_array2"));
}
TEST_F(ValidateCapability,
@@ -2347,8 +2347,8 @@ OpFunctionEnd
EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("operand 5568 requires one of these extensions: "
"SPV_INTEL_subgroups"));
HasSubstr("operand SubgroupShuffleINTEL(5568) requires one of "
"these extensions: SPV_INTEL_subgroups"));
}
TEST_F(ValidateCapability,
@@ -2407,6 +2407,33 @@ OpMemoryModel Logical GLSL450
"specified if the VulkanKHR memory model is used"));
}
// In the grammar, SubgroupEqMask and SubgroupMaskKHR have different enabling
// lists of extensions.
TEST_F(ValidateCapability, SubgroupEqMaskEnabledByExtension) {
const std::string spirv = R"(
OpCapability Shader
OpCapability SubgroupBallotKHR
OpExtension "SPV_KHR_shader_ballot"
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %main "main"
OpDecorate %var BuiltIn SubgroupEqMask
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%ptr_uint = OpTypePointer Private %uint
%var = OpVariable %ptr_uint Private
%fn = OpTypeFunction %void
%main = OpFunction %void None %fn
%entry = OpLabel
%val = OpLoad %uint %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_0);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_0))
<< getDiagnosticString();
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -1952,7 +1952,7 @@ TEST_F(ValidateCFG, LoopWithZeroBackEdgesBad) {
%funct = OpTypeFunction %voidt
%main = OpFunction %voidt None %funct
%loop = OpLabel
OpLoopMerge %exit %exit None
OpLoopMerge %exit %loop None
OpBranch %exit
%exit = OpLabel
OpReturn
@@ -3028,6 +3028,381 @@ OpFunctionEnd
"to the loop header <ID> 7"));
}
TEST_F(ValidateCFG, LoopMergeMergeBlockNotLabel) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
OpLoopMerge %undef %2 None
OpBranchConditional %undef %2 %2
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Merge Block 1[%undef] must be an OpLabel"));
}
TEST_F(ValidateCFG, LoopMergeContinueTargetNotLabel) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
OpLoopMerge %2 %undef None
OpBranchConditional %undef %2 %2
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Continue Target 1[%undef] must be an OpLabel"));
}
TEST_F(ValidateCFG, LoopMergeMergeBlockContinueTargetSameLabel) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
OpLoopMerge %2 %2 None
OpBranchConditional %undef %2 %2
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Merge Block and Continue Target must be different ids"));
}
TEST_F(ValidateCFG, LoopMergeUnrollAndDontUnroll) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%5 = OpLabel
OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 Unroll|DontUnroll
OpBranchConditional %undef %2 %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Unroll and DontUnroll loop controls must not both be specified"));
}
TEST_F(ValidateCFG, LoopMergePeelCountAndDontUnroll) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%5 = OpLabel
OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 DontUnroll|PeelCount 1
OpBranchConditional %undef %2 %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"PeelCount and DontUnroll loop controls must not both be specified"));
}
TEST_F(ValidateCFG, LoopMergePartialCountAndDontUnroll) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%5 = OpLabel
OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 DontUnroll|PartialCount 1
OpBranchConditional %undef %2 %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("PartialCount and DontUnroll loop controls must not "
"both be specified"));
}
TEST_F(ValidateCFG, LoopMergeIterationMultipleZero) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%5 = OpLabel
OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 IterationMultiple 0
OpBranchConditional %undef %2 %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"IterationMultiple loop control operand must be greater than zero"));
}
TEST_F(ValidateCFG, LoopMergeIterationMultipleZeroMoreOperands) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpName %undef "undef"
%void = OpTypeVoid
%bool = OpTypeBool
%undef = OpUndef %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%5 = OpLabel
OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 MaxIterations|IterationMultiple 4 0
OpBranchConditional %undef %2 %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"IterationMultiple loop control operand must be greater than zero"));
}
TEST_F(ValidateCFG, InvalidSelectionExit) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantTrue %3
%5 = OpTypeFunction %2
%1 = OpFunction %2 None %5
%6 = OpLabel
OpSelectionMerge %7 None
OpBranchConditional %4 %7 %8
%8 = OpLabel
OpSelectionMerge %9 None
OpBranchConditional %4 %10 %9
%10 = OpLabel
OpBranch %7
%9 = OpLabel
OpBranch %7
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("block <ID> 10[%10] exits the selection headed by <ID> "
"8[%8], but not via a structured exit"));
}
TEST_F(ValidateCFG, InvalidLoopExit) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantTrue %3
%5 = OpTypeFunction %2
%1 = OpFunction %2 None %5
%6 = OpLabel
OpSelectionMerge %7 None
OpBranchConditional %4 %7 %8
%8 = OpLabel
OpLoopMerge %9 %10 None
OpBranchConditional %4 %9 %11
%11 = OpLabel
OpBranchConditional %4 %7 %10
%10 = OpLabel
OpBranch %8
%9 = OpLabel
OpBranch %7
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("block <ID> 11[%11] exits the loop headed by <ID> "
"8[%8], but not via a structured exit"));
}
TEST_F(ValidateCFG, InvalidContinueExit) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %1 "main"
OpExecutionMode %1 OriginUpperLeft
%2 = OpTypeVoid
%3 = OpTypeBool
%4 = OpConstantTrue %3
%5 = OpTypeFunction %2
%1 = OpFunction %2 None %5
%6 = OpLabel
OpSelectionMerge %7 None
OpBranchConditional %4 %7 %8
%8 = OpLabel
OpLoopMerge %9 %10 None
OpBranchConditional %4 %9 %10
%10 = OpLabel
OpBranch %11
%11 = OpLabel
OpBranchConditional %4 %8 %7
%9 = OpLabel
OpBranch %7
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("block <ID> 11[%11] exits the continue headed by <ID> "
"10[%10], but not via a structured exit"));
}
TEST_F(ValidateCFG, InvalidSelectionExitBackedge) {
const std::string text = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%1 = OpTypeVoid
%2 = OpTypeBool
%3 = OpUndef %2
%4 = OpTypeFunction %1
%5 = OpFunction %1 None %4
%6 = OpLabel
OpBranch %7
%7 = OpLabel
OpLoopMerge %8 %9 None
OpBranchConditional %3 %8 %9
%9 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %3 %11 %12
%11 = OpLabel
OpBranch %13
%12 = OpLabel
OpBranch %13
%13 = OpLabel
OpBranch %7
%10 = OpLabel
OpUnreachable
%8 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text);
EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("block <ID> 13[%13] exits the selection headed by <ID> "
"9[%9], but not via a structured exit"));
}
/// TODO(umar): Nested CFG constructs
} // namespace

View File

@@ -1568,6 +1568,209 @@ OpFunctionEnd
HasSubstr("Expected Index to be int scalar"));
}
TEST_F(ValidateComposites, CopyLogicalSameType) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%struct = OpTypeStruct
%const_struct = OpConstantComposite %struct
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %struct %const_struct
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result Type must not equal the Operand type"));
}
TEST_F(ValidateComposites, CopyLogicalSameStructDifferentId) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%struct1 = OpTypeStruct
%struct2 = OpTypeStruct
%const_struct = OpConstantComposite %struct1
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %struct2 %const_struct
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateComposites, CopyLogicalArrayDifferentLength) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%int_5 = OpConstant %int 5
%array1 = OpTypeArray %int %int_4
%array2 = OpTypeArray %int %int_5
%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %array2 %const_array
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result Type does not logically match the Operand type"));
}
TEST_F(ValidateComposites, CopyLogicalArrayDifferentElement) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%float = OpTypeFloat 32
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%array1 = OpTypeArray %int %int_4
%array2 = OpTypeArray %float %int_4
%const_array = OpConstantComposite %array1 %int_4 %int_4 %int_4 %int_4
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %array2 %const_array
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result Type does not logically match the Operand type"));
}
TEST_F(ValidateComposites, CopyLogicalArrayLogicallyMatchedElement) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%float = OpTypeFloat 32
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%inner1 = OpTypeArray %int %int_1
%inner2 = OpTypeArray %int %int_1
%array1 = OpTypeArray %inner1 %int_1
%array2 = OpTypeArray %inner2 %int_1
%const_inner = OpConstantComposite %inner1 %int_1
%const_array = OpConstantComposite %array1 %const_inner
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %array2 %const_array
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateComposites, CopyLogicalStructDifferentNumberElements) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct1 = OpTypeStruct
%struct2 = OpTypeStruct %int
%const_struct = OpConstantComposite %struct1
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %struct2 %const_struct
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result Type does not logically match the Operand type"));
}
TEST_F(ValidateComposites, CopyLogicalStructDifferentElement) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint_0 = OpConstant %uint 0
%struct1 = OpTypeStruct %int %uint
%struct2 = OpTypeStruct %int %int
%const_struct = OpConstantComposite %struct1 %int_0 %uint_0
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %struct2 %const_struct
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Result Type does not logically match the Operand type"));
}
TEST_F(ValidateComposites, CopyLogicalStructLogicallyMatch) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%array1 = OpTypeArray %int %int_1
%array2 = OpTypeArray %int %int_1
%struct1 = OpTypeStruct %int %array1
%struct2 = OpTypeStruct %int %array2
%const_array = OpConstantComposite %array1 %int_1
%const_struct = OpConstantComposite %struct1 %int_1 %const_array
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%copy = OpCopyLogical %struct2 %const_struct
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -198,6 +198,60 @@ INSTANTIATE_TEST_SUITE_P(
"%v = OpSpecConstantOp %bool SGreaterThanEqual %uint_0 %uint_0"),
}));
INSTANTIATE_TEST_SUITE_P(
UConvert, ValidateConstantOp,
ValuesIn(std::vector<ConstantOpCase>{
// TODO(dneto): Conversions must change width.
{SPV_ENV_UNIVERSAL_1_0,
kKernelPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
{SPV_ENV_UNIVERSAL_1_1,
kKernelPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
{SPV_ENV_UNIVERSAL_1_3,
kKernelPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
{SPV_ENV_UNIVERSAL_1_3,
kKernelPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
{SPV_ENV_UNIVERSAL_1_4,
kKernelPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
{SPV_ENV_UNIVERSAL_1_0,
kShaderPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
false,
"Prior to SPIR-V 1.4, specialization constant operation "
"UConvert requires Kernel capability"},
{SPV_ENV_UNIVERSAL_1_1,
kShaderPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
false,
"Prior to SPIR-V 1.4, specialization constant operation "
"UConvert requires Kernel capability"},
{SPV_ENV_UNIVERSAL_1_3,
kShaderPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
false,
"Prior to SPIR-V 1.4, specialization constant operation "
"UConvert requires Kernel capability"},
{SPV_ENV_UNIVERSAL_1_3,
kShaderPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
false,
"Prior to SPIR-V 1.4, specialization constant operation "
"UConvert requires Kernel capability"},
{SPV_ENV_UNIVERSAL_1_4,
kShaderPreamble kBasicTypes
"%v = OpSpecConstantOp %uint UConvert %uint_0",
true, ""},
}));
INSTANTIATE_TEST_SUITE_P(
KernelInKernel, ValidateConstantOp,
ValuesIn(std::vector<ConstantOpCase>{
@@ -294,6 +348,25 @@ INSTANTIATE_TEST_SUITE_P(
true, ""},
}));
TEST_F(ValidateConstant, SpecConstantUConvert1p3Binary1p4EnvBad) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int0 = OpConstant %int 0
%const = OpSpecConstantOp %int UConvert %int0
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Prior to SPIR-V 1.4, specialization constant operation UConvert "
"requires Kernel capability or extension SPV_AMD_gpu_shader_int16"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -778,9 +778,10 @@ TEST_F(ValidateData, vulkan_disallow_free_fp_rounding_mode) {
ASSERT_EQ(SPV_ERROR_INVALID_CAPABILITY, ValidateInstructions(env));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Operand 2 of Decorate requires one of these capabilities: "
"StorageBuffer16BitAccess StorageUniform16 "
"StoragePushConstant16 StorageInputOutput16"));
HasSubstr(
"Operand 2 of Decorate requires one of these capabilities: "
"StorageBuffer16BitAccess UniformAndStorageBuffer16BitAccess "
"StoragePushConstant16 StorageInputOutput16"));
}
}
}

View File

@@ -19,6 +19,7 @@
#include "gmock/gmock.h"
#include "source/val/decoration.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
#include "test/val/val_code_generator.h"
#include "test/val/val_fixtures.h"
@@ -2099,7 +2100,7 @@ TEST_F(ValidateDecorations, BufferBlock16bitStandardStorageBufferLayout) {
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, BlockArrayBaseAlignmentGood) {
TEST_F(ValidateDecorations, BlockArrayExtendedAlignmentGood) {
// For uniform buffer, Array base alignment is 16, and ArrayStride
// must be a multiple of 16.
std::string spirv = R"(
@@ -2132,7 +2133,7 @@ TEST_F(ValidateDecorations, BlockArrayBaseAlignmentGood) {
<< getDiagnosticString();
}
TEST_F(ValidateDecorations, BlockArrayBadAlignmentBad) {
TEST_F(ValidateDecorations, BlockArrayBaseAlignmentBad) {
// For uniform buffer, Array base alignment is 16.
std::string spirv = R"(
OpCapability Shader
@@ -2169,7 +2170,7 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentBad) {
"member 1 at offset 8 is not aligned to 16"));
}
TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithRelaxedLayoutStillBad) {
// For uniform buffer, Array base alignment is 16, and ArrayStride
// must be a multiple of 16. This case uses relaxed block layout. Relaxed
// layout only relaxes rules for vector alignment, not array alignment.
@@ -2212,7 +2213,7 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithRelaxedLayoutStillBad) {
"member 1 at offset 8 is not aligned to 16"));
}
TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
TEST_F(ValidateDecorations, BlockArrayBaseAlignmentWithVulkan1_1StillBad) {
// Same as previous test, but with Vulkan 1.1, which includes
// VK_KHR_relaxed_block_layout in core.
std::string spirv = R"(
@@ -2253,6 +2254,44 @@ TEST_F(ValidateDecorations, BlockArrayBadAlignmentWithVulkan1_1StillBad) {
"member 1 at offset 8 is not aligned to 16"));
}
TEST_F(ValidateDecorations,
BlockArrayBaseAlignmentWithBlockStandardLayoutGood) {
// Same as previous test, but with VK_KHR_uniform_buffer_standard_layout
std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main"
OpSource GLSL 450
OpDecorate %_arr_float_uint_2 ArrayStride 16
OpDecorate %u DescriptorSet 0
OpDecorate %u Binding 0
OpMemberDecorate %S 0 Offset 0
OpMemberDecorate %S 1 Offset 8
OpDecorate %S Block
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%uint = OpTypeInt 32 0
%uint_2 = OpConstant %uint 2
%_arr_float_uint_2 = OpTypeArray %float %uint_2
%S = OpTypeStruct %v2float %_arr_float_uint_2
%_ptr_Uniform_S = OpTypePointer Uniform %S
%u = OpVariable %_ptr_Uniform_S Uniform
%main = OpFunction %void None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetUniformBufferStandardLayout(getValidatorOptions(),
true);
EXPECT_EQ(SPV_SUCCESS,
ValidateAndRetrieveValidationState(SPV_ENV_VULKAN_1_0));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, VulkanBufferBlockOnStorageBufferBad) {
std::string spirv = R"(
OpCapability Shader
@@ -4708,7 +4747,7 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
// Uniform decoration
// Uniform and UniformId decorations
TEST_F(ValidateDecorations, UniformDecorationGood) {
const std::string spirv = R"(
@@ -4737,49 +4776,96 @@ OpFunctionEnd
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %fn Uniform
%void = OpTypeVoid
%fn = OpTypeFunction %void
%main = OpFunction %void None %fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Uniform decoration applied to a non-object"));
EXPECT_THAT(getDiagnosticString(), HasSubstr("%2 = OpTypeFunction %void"));
}
TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
const std::string spirv = R"(
// Returns SPIR-V assembly for a shader that uses a given decoration
// instruction.
std::string ShaderWithUniformLikeDecoration(const std::string& inst) {
return std::string(R"(
OpCapability Shader
OpMemoryModel Logical Simple
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %subgroupscope "subgroupscope"
OpName %call "call"
OpName %myfunc "myfunc"
OpDecorate %call Uniform
OpName %int0 "int0"
OpName %float0 "float0"
OpName %fn "fn"
)") + inst +
R"(
%void = OpTypeVoid
%fnty = OpTypeFunction %void
%myfunc = OpFunction %void None %fnty
%float = OpTypeFloat 32
%int = OpTypeInt 32 1
%int0 = OpConstantNull %int
%int_99 = OpConstant %int 99
%subgroupscope = OpConstant %int 3
%float0 = OpConstantNull %float
%fn = OpTypeFunction %void
%myfunc = OpFunction %void None %fn
%myfuncentry = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %fnty
%main = OpFunction %void None %fn
%entry = OpLabel
%call = OpFunctionCall %void %myfunc
OpReturn
OpFunctionEnd
)";
}
TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13Bad) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorateId %int0 UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("requires SPIR-V version 1.4 or later\n"
" OpDecorateId %int0 UniformId %subgroupscope"));
}
TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV13BadTargetV14) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorateId %int0 UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("requires SPIR-V version 1.4 or later"));
}
TEST_F(ValidateDecorations, UniformIdDecorationWithScopeIdV14Good) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorateId %int0 UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, UniformDecorationTargetsTypeBad) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorate %fn Uniform");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Uniform decoration applied to a non-object"));
EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
}
TEST_F(ValidateDecorations, UniformIdDecorationTargetsTypeBad) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorateId %fn UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("UniformId decoration applied to a non-object"));
EXPECT_THAT(getDiagnosticString(), HasSubstr("%fn = OpTypeFunction %void"));
}
TEST_F(ValidateDecorations, UniformDecorationTargetsVoidValueBad) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorate %call Uniform");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
@@ -4788,6 +4874,82 @@ OpFunctionEnd
" %call = OpFunctionCall %void %myfunc"));
}
TEST_F(ValidateDecorations, UniformIdDecorationTargetsVoidValueBad) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorateId %call UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4))
<< spirv;
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("UniformId decoration applied to a value with void type\n"
" %call = OpFunctionCall %void %myfunc"));
}
TEST_F(ValidateDecorations,
UniformDecorationWithScopeIdV14IdIsFloatValueIsBad) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %float0");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("ConstantNull: expected Execution Scope to be a 32-bit int"));
}
TEST_F(ValidateDecorations,
UniformDecorationWithScopeIdV14IdIsInvalidIntValueBad) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int_99");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid scope value:\n %int_99 = OpConstant %int 99\n"));
}
TEST_F(ValidateDecorations, UniformDecorationWithScopeIdV14VulkanEnv) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorateId %int0 UniformId %int0");
CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1_SPIRV_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr(": in Vulkan environment Execution Scope is limited to "
"Workgroup and Subgroup"));
}
TEST_F(ValidateDecorations, UniformDecorationWithWrongInstructionBad) {
const std::string spirv =
ShaderWithUniformLikeDecoration("OpDecorateId %int0 Uniform");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Decorations that don't take ID parameters may not be "
"used with OpDecorateId\n"
" OpDecorateId %int0 Uniform"));
}
TEST_F(ValidateDecorations, UniformIdDecorationWithWrongInstructionBad) {
const std::string spirv = ShaderWithUniformLikeDecoration(
"OpDecorate %int0 UniformId %subgroupscope");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Decorations taking ID parameters may not be used with OpDecorateId\n"
" OpDecorate %int0 UniformId %subgroupscope"));
}
TEST_F(ValidateDecorations, MultipleOffsetDecorationsOnSameID) {
std::string spirv = R"(
OpCapability Shader
@@ -5070,6 +5232,26 @@ TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionBad) {
"SPV_KHR_no_integer_wrap_decoration"));
}
TEST_F(ValidateDecorations, NoSignedWrapRequiresExtensionV13Bad) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
"%val = OpIAdd %int %zero %zero", "");
CompileSuccessfully(spirv);
EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("requires one of these extensions: "
"SPV_KHR_no_integer_wrap_decoration"));
}
TEST_F(ValidateDecorations, NoSignedWrapOkInSPV14Good) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
"%val = OpIAdd %int %zero %zero", "");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, NoSignedWrapIAddGood) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoSignedWrap",
"%val = OpIAdd %int %zero %zero");
@@ -5192,6 +5374,26 @@ TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionBad) {
"SPV_KHR_no_integer_wrap_decoration"));
}
TEST_F(ValidateDecorations, NoUnsignedWrapRequiresExtensionV13Bad) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
"%val = OpIAdd %int %zero %zero", "");
CompileSuccessfully(spirv);
EXPECT_NE(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("requires one of these extensions: "
"SPV_KHR_no_integer_wrap_decoration"));
}
TEST_F(ValidateDecorations, NoUnsignedWrapOkInSPV14Good) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
"%val = OpIAdd %int %zero %zero", "");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, NoUnsignedWrapIAddGood) {
std::string spirv = MakeIntegerShader("OpDecorate %val NoUnsignedWrap",
"%val = OpIAdd %int %zero %zero");
@@ -5783,10 +5985,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
OpName %simple_struct "simple_struct"
OpDecorate %struct_b Block
OpDecorate %struct_bb BufferBlock
OpDecorate %struct_b_rtarr Block
OpMemberDecorate %struct_b 0 Offset 0
OpMemberDecorate %struct_bb 0 Offset 0
OpMemberDecorate %struct_b_rtarr 0 Offset 0
OpDecorate %rtarr ArrayStride 4
)") + decoration_inst +
@@ -5800,7 +6000,6 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%int = OpTypeInt 32 0
%int_2 = OpConstant %int 2
%struct_b = OpTypeStruct %float
%struct_bb = OpTypeStruct %float
%rtarr = OpTypeRuntimeArray %float
%struct_b_rtarr = OpTypeStruct %rtarr
%simple_struct = OpTypeStruct %float
@@ -5812,7 +6011,6 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%rta_imstor = OpTypeRuntimeArray %imstor
%_ptr_Uniform_stb = OpTypePointer Uniform %struct_b
%_ptr_Uniform_stbb = OpTypePointer Uniform %struct_bb
%_ptr_StorageBuffer_stb = OpTypePointer StorageBuffer %struct_b
%_ptr_StorageBuffer_stb_rtarr = OpTypePointer StorageBuffer %struct_b_rtarr
%_ptr_Workgroup = OpTypePointer Workgroup %float
@@ -5826,7 +6024,6 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
%var_ubo = OpVariable %_ptr_Uniform_stb Uniform
%var_ssbo_u = OpVariable %_ptr_Uniform_stbb Uniform
%var_ssbo_sb = OpVariable %_ptr_StorageBuffer_stb StorageBuffer
%var_ssbo_sb_rtarr = OpVariable %_ptr_StorageBuffer_stb_rtarr StorageBuffer
%var_wg = OpVariable %_ptr_Workgroup Workgroup
@@ -5946,7 +6143,24 @@ TEST_F(ValidateDecorations, NonWritableVarUboGood) {
}
TEST_F(ValidateDecorations, NonWritableVarSsboInUniformGood) {
std::string spirv = ShaderWithNonWritableTarget("%var_ssbo_u");
const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main"
OpDecorate %struct_bb BufferBlock
OpMemberDecorate %struct_bb 0 Offset 0
OpDecorate %var_ssbo_u NonWritable
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%struct_bb = OpTypeStruct %float
%_ptr_Uniform_stbb = OpTypePointer Uniform %struct_bb
%var_ssbo_u = OpVariable %_ptr_Uniform_stbb Uniform
%main = OpFunction %void None %void_fn
%label = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
@@ -5987,6 +6201,18 @@ TEST_F(ValidateDecorations, NonWritableVarWorkgroupBad) {
"buffer\n %var_wg"));
}
TEST_F(ValidateDecorations, NonWritableVarWorkgroupV14Bad) {
std::string spirv = ShaderWithNonWritableTarget("%var_wg");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Target of NonWritable decoration is invalid: must "
"point to a storage image, uniform block, storage "
"buffer, or variable in Private or Function storage "
"class\n %var_wg"));
}
TEST_F(ValidateDecorations, NonWritableVarPrivateBad) {
std::string spirv = ShaderWithNonWritableTarget("%var_priv");
@@ -5998,6 +6224,36 @@ TEST_F(ValidateDecorations, NonWritableVarPrivateBad) {
"buffer\n %var_priv"));
}
TEST_F(ValidateDecorations, NonWritableVarPrivateV13Bad) {
std::string spirv = ShaderWithNonWritableTarget("%var_priv");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Target of NonWritable decoration is invalid: must "
"point to a storage image, uniform block, or storage "
"buffer\n %var_priv"));
}
TEST_F(ValidateDecorations, NonWritableVarPrivateV14Good) {
std::string spirv = ShaderWithNonWritableTarget("%var_priv");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, NonWritableVarPrivateV13TargetV14Bad) {
std::string spirv = ShaderWithNonWritableTarget("%var_priv");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Target of NonWritable decoration is invalid: must "
"point to a storage image, uniform block, or storage "
"buffer\n %var_priv"));
}
TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
std::string spirv = ShaderWithNonWritableTarget("%var_func");
@@ -6084,6 +6340,66 @@ INSTANTIATE_TEST_SUITE_P(
SPV_ERROR_INVALID_ID,
"is not valid for the WebGPU execution environment."))));
TEST_F(ValidateDecorations, NonWritableVarFunctionV13Bad) {
std::string spirv = ShaderWithNonWritableTarget("%var_func");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Target of NonWritable decoration is invalid: must "
"point to a storage image, uniform block, or storage "
"buffer\n %var_func"));
}
TEST_F(ValidateDecorations, NonWritableVarFunctionV14Good) {
std::string spirv = ShaderWithNonWritableTarget("%var_func");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateDecorations, NonWritableVarFunctionV13TargetV14Bad) {
std::string spirv = ShaderWithNonWritableTarget("%var_func");
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Target of NonWritable decoration is invalid: must "
"point to a storage image, uniform block, or storage "
"buffer\n %var_func"));
}
TEST_F(ValidateDecorations, BufferBlockV13ValV14Good) {
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %1 BufferBlock
%1 = OpTypeStruct
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateDecorations, BufferBlockV14Bad) {
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpDecorate %1 BufferBlock
%1 = OpTypeStruct
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_WRONG_VERSION,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("2nd operand of Decorate: operand BufferBlock(3) "
"requires SPIR-V version 1.3 or earlier"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -288,9 +288,12 @@ TEST_P(ValidateExtIntoCore, DoNotAskForExtensionInLaterVersion) {
CompileSuccessfully(code.c_str(), GetParam().env);
if (GetParam().success) {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env));
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(GetParam().env))
<< getDiagnosticString();
} else {
ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env));
ASSERT_NE(SPV_SUCCESS, ValidateInstructions(GetParam().env))
<< " in " << spvTargetEnvDescription(GetParam().env) << ":\n"
<< code;
const std::string message = getDiagnosticString();
if (spvIsVulkanEnv(GetParam().env)) {
EXPECT_THAT(message, HasSubstr(std::string(GetParam().cap) +

View File

@@ -21,6 +21,7 @@
#include <string>
#include "source/val/validation_state.h"
#include "spirv-tools/libspirv.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
@@ -37,6 +38,11 @@ class ValidateBase : public ::testing::Test,
// Returns the a spv_const_binary struct
spv_const_binary get_const_binary();
// Assembles the given SPIR-V text, checks that it fails to assemble,
// and returns resulting diagnostic. No internal state is updated.
std::string CompileFailure(std::string code,
spv_target_env env = SPV_ENV_UNIVERSAL_1_0);
// Checks that 'code' is valid SPIR-V text representation and stores the
// binary version for further method calls.
void CompileSuccessfully(std::string code,
@@ -100,17 +106,31 @@ void ValidateBase<T>::TearDown() {
spvValidatorOptionsDestroy(options_);
}
template <typename T>
std::string ValidateBase<T>::CompileFailure(std::string code,
spv_target_env env) {
spv_diagnostic diagnostic = nullptr;
EXPECT_NE(SPV_SUCCESS,
spvTextToBinary(ScopedContext(env).context, code.c_str(),
code.size(), &binary_, &diagnostic));
std::string result(diagnostic->error);
spvDiagnosticDestroy(diagnostic);
return result;
}
template <typename T>
void ValidateBase<T>::CompileSuccessfully(std::string code,
spv_target_env env) {
DestroyBinary();
spv_diagnostic diagnostic = nullptr;
ASSERT_EQ(SPV_SUCCESS,
spvTextToBinary(ScopedContext(env).context, code.c_str(),
code.size(), &binary_, &diagnostic))
ScopedContext context(env);
auto status = spvTextToBinary(context.context, code.c_str(), code.size(),
&binary_, &diagnostic);
EXPECT_EQ(SPV_SUCCESS, status)
<< "ERROR: " << diagnostic->error
<< "\nSPIR-V could not be compiled into binary:\n"
<< code;
ASSERT_EQ(SPV_SUCCESS, status);
spvDiagnosticDestroy(diagnostic);
}

View File

@@ -416,6 +416,423 @@ TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationVariablePointers) {
}
}
TEST_F(ValidateFunctionCall, LogicallyMatchingPointers) {
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpDecorate %_runtimearr__struct_3 ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpDecorate %_struct_5 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %int
%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
%_struct_5 = OpTypeStruct %_runtimearr__struct_3
%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_struct_15 = OpTypeStruct %int
%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
%18 = OpTypeFunction %void %_ptr_Function__struct_15
%2 = OpVariable %_ptr_Uniform__struct_5 Uniform
%1 = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
%21 = OpFunctionCall %void %22 %20
OpReturn
OpFunctionEnd
%22 = OpFunction %void None %18
%23 = OpFunctionParameter %_ptr_Function__struct_15
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateFunctionCall, LogicallyMatchingPointersNestedStruct) {
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpMemberDecorate %_struct_4 0 Offset 0
OpDecorate %_runtimearr__struct_4 ArrayStride 4
OpMemberDecorate %_struct_6 0 Offset 0
OpDecorate %_struct_6 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %int
%_struct_4 = OpTypeStruct %_struct_3
%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
%_struct_6 = OpTypeStruct %_runtimearr__struct_4
%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
%void = OpTypeVoid
%13 = OpTypeFunction %void
%_struct_14 = OpTypeStruct %int
%_struct_15 = OpTypeStruct %_struct_14
%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%18 = OpTypeFunction %void %_ptr_Function__struct_15
%2 = OpVariable %_ptr_Uniform__struct_6 Uniform
%1 = OpFunction %void None %13
%19 = OpLabel
%20 = OpVariable %_ptr_Function__struct_15 Function
%21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
%22 = OpFunctionCall %void %23 %21
OpReturn
OpFunctionEnd
%23 = OpFunction %void None %18
%24 = OpFunctionParameter %_ptr_Function__struct_15
%25 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateFunctionCall, LogicallyMatchingPointersNestedArray) {
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpDecorate %_arr_int_uint_10 ArrayStride 4
OpMemberDecorate %_struct_4 0 Offset 0
OpDecorate %_runtimearr__struct_4 ArrayStride 40
OpMemberDecorate %_struct_6 0 Offset 0
OpDecorate %_struct_6 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_10 = OpConstant %uint 10
%_arr_int_uint_10 = OpTypeArray %int %uint_10
%_struct_4 = OpTypeStruct %_arr_int_uint_10
%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
%_struct_6 = OpTypeStruct %_runtimearr__struct_4
%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%_arr_int_uint_10_0 = OpTypeArray %int %uint_10
%_struct_17 = OpTypeStruct %_arr_int_uint_10_0
%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
%19 = OpTypeFunction %void %_ptr_Function__struct_17
%2 = OpVariable %_ptr_Uniform__struct_6 Uniform
%1 = OpFunction %void None %14
%20 = OpLabel
%21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
%22 = OpFunctionCall %void %23 %21
OpReturn
OpFunctionEnd
%23 = OpFunction %void None %19
%24 = OpFunctionParameter %_ptr_Function__struct_17
%25 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersMissingMember) {
// Validation should fail because the formal parameter type has two members,
// while the actual parameter only has 1.
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpDecorate %_runtimearr__struct_3 ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpDecorate %_struct_5 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %int
%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
%_struct_5 = OpTypeStruct %_runtimearr__struct_3
%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_struct_15 = OpTypeStruct %int %int
%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
%18 = OpTypeFunction %void %_ptr_Function__struct_15
%2 = OpVariable %_ptr_Uniform__struct_5 Uniform
%1 = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
%21 = OpFunctionCall %void %22 %20
OpReturn
OpFunctionEnd
%22 = OpFunction %void None %18
%23 = OpFunctionParameter %_ptr_Function__struct_15
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("type does not match Function <id>"));
}
TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersDifferentMemberType) {
// Validation should fail because the formal parameter has a member that is
// a different type than the actual parameter.
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpDecorate %_runtimearr__struct_3 ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpDecorate %_struct_5 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %uint
%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
%_struct_5 = OpTypeStruct %_runtimearr__struct_3
%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_struct_15 = OpTypeStruct %int
%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
%18 = OpTypeFunction %void %_ptr_Function__struct_15
%2 = OpVariable %_ptr_Uniform__struct_5 Uniform
%1 = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
%21 = OpFunctionCall %void %22 %20
OpReturn
OpFunctionEnd
%22 = OpFunction %void None %18
%23 = OpFunctionParameter %_ptr_Function__struct_15
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("type does not match Function <id>"));
}
TEST_F(ValidateFunctionCall,
LogicallyMismatchedPointersIncompatableDecorations) {
// Validation should fail because the formal parameter has an incompatible
// decoration.
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpDecorate %_runtimearr__struct_3 ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpDecorate %_struct_5 Block
OpMemberDecorate %_struct_15 0 NonWritable
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %int
%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
%_struct_5 = OpTypeStruct %_runtimearr__struct_3
%_ptr_StorageBuffer__struct_5 = OpTypePointer StorageBuffer %_struct_5
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_struct_15 = OpTypeStruct %int
%_ptr_Function__struct_15 = OpTypePointer Function %_struct_15
%_ptr_StorageBuffer__struct_3 = OpTypePointer StorageBuffer %_struct_3
%18 = OpTypeFunction %void %_ptr_Function__struct_15
%2 = OpVariable %_ptr_StorageBuffer__struct_5 StorageBuffer
%1 = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_StorageBuffer__struct_3 %2 %int_0 %uint_0
%21 = OpFunctionCall %void %22 %20
OpReturn
OpFunctionEnd
%22 = OpFunction %void None %18
%23 = OpFunctionParameter %_ptr_Function__struct_15
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("type does not match Function <id>"));
}
TEST_F(ValidateFunctionCall,
LogicallyMismatchedPointersIncompatableDecorations2) {
// Validation should fail because the formal parameter has an incompatible
// decoration.
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpMemberDecorate %_struct_3 0 Offset 0
OpDecorate %_runtimearr__struct_3 ArrayStride 4
OpMemberDecorate %_struct_5 0 Offset 0
OpDecorate %_struct_5 BufferBlock
OpDecorate %_ptr_Uniform__struct_3 ArrayStride 4
OpDecorate %_ptr_Uniform__struct_3_0 ArrayStride 8
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_struct_3 = OpTypeStruct %int
%_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
%_struct_5 = OpTypeStruct %_runtimearr__struct_3
%_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform__struct_3 = OpTypePointer Uniform %_struct_3
%_ptr_Uniform__struct_3_0 = OpTypePointer Uniform %_struct_3
%18 = OpTypeFunction %void %_ptr_Uniform__struct_3_0
%2 = OpVariable %_ptr_Uniform__struct_5 Uniform
%1 = OpFunction %void None %14
%19 = OpLabel
%20 = OpAccessChain %_ptr_Uniform__struct_3 %2 %int_0 %uint_0
%21 = OpFunctionCall %void %22 %20
OpReturn
OpFunctionEnd
%22 = OpFunction %void None %18
%23 = OpFunctionParameter %_ptr_Uniform__struct_3_0
%24 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("type does not match Function <id>"));
}
TEST_F(ValidateFunctionCall, LogicallyMismatchedPointersArraySize) {
// Validation should fail because the formal parameter array has a different
// number of element than the actual parameter.
std::string spirv =
R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %1 "main"
OpExecutionMode %1 LocalSize 1 1 1
OpSource HLSL 600
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpDecorate %_arr_int_uint_10 ArrayStride 4
OpMemberDecorate %_struct_4 0 Offset 0
OpDecorate %_runtimearr__struct_4 ArrayStride 40
OpMemberDecorate %_struct_6 0 Offset 0
OpDecorate %_struct_6 BufferBlock
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_5 = OpConstant %uint 5
%uint_10 = OpConstant %uint 10
%_arr_int_uint_10 = OpTypeArray %int %uint_10
%_struct_4 = OpTypeStruct %_arr_int_uint_10
%_runtimearr__struct_4 = OpTypeRuntimeArray %_struct_4
%_struct_6 = OpTypeStruct %_runtimearr__struct_4
%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
%void = OpTypeVoid
%14 = OpTypeFunction %void
%_ptr_Uniform__struct_4 = OpTypePointer Uniform %_struct_4
%_arr_int_uint_5 = OpTypeArray %int %uint_5
%_struct_17 = OpTypeStruct %_arr_int_uint_5
%_ptr_Function__struct_17 = OpTypePointer Function %_struct_17
%19 = OpTypeFunction %void %_ptr_Function__struct_17
%2 = OpVariable %_ptr_Uniform__struct_6 Uniform
%1 = OpFunction %void None %14
%20 = OpLabel
%21 = OpAccessChain %_ptr_Uniform__struct_4 %2 %int_0 %uint_0
%22 = OpFunctionCall %void %23 %21
OpReturn
OpFunctionEnd
%23 = OpFunction %void None %19
%24 = OpFunctionParameter %_ptr_Function__struct_17
%25 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
spvValidatorOptionsSetBeforeHlslLegalization(getValidatorOptions(), true);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("OpFunctionCall Argument <id>"));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("type does not match Function <id>"));
}
INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
Values("UniformConstant", "Input", "Uniform", "Output",
"Workgroup", "Private", "Function",

View File

@@ -25,6 +25,7 @@ namespace spvtools {
namespace val {
namespace {
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Not;
@@ -54,9 +55,33 @@ OpCapability ImageBuffer
ss << "OpCapability SampledRect\n";
}
// In 1.4, the entry point must list all module-scope variables used. Just
// list all of them.
std::string interface_vars = (env != SPV_ENV_UNIVERSAL_1_4) ? "" :
R"(
%uniform_image_f32_1d_0001
%uniform_image_f32_1d_0002_rgba32f
%uniform_image_f32_2d_0001
%uniform_image_f32_2d_0010
%uniform_image_u32_2d_0001
%uniform_image_u32_2d_0000
%uniform_image_s32_3d_0001
%uniform_image_f32_2d_0002
%uniform_image_s32_2d_0002
%uniform_image_f32_spd_0002
%uniform_image_f32_3d_0111
%uniform_image_f32_cube_0101
%uniform_image_f32_cube_0102_rgba32f
%uniform_sampler
%private_image_u32_buffer_0002_r32ui
%private_image_u32_spd_0002
%private_image_f32_buffer_0002_r32ui
)";
ss << capabilities_and_extensions;
ss << "OpMemoryModel Logical " << memory_model << "\n";
ss << "OpEntryPoint " << execution_model << " %main \"main\"\n";
ss << "OpEntryPoint " << execution_model
<< " %main \"main\" " + interface_vars + "\n";
if (execution_model == "Fragment") {
ss << "OpExecutionMode %main OriginUpperLeft\n";
}
@@ -79,6 +104,8 @@ OpDecorate %uniform_image_s32_3d_0001 DescriptorSet 1
OpDecorate %uniform_image_s32_3d_0001 Binding 2
OpDecorate %uniform_image_f32_2d_0002 DescriptorSet 1
OpDecorate %uniform_image_f32_2d_0002 Binding 3
OpDecorate %uniform_image_s32_2d_0002 DescriptorSet 1
OpDecorate %uniform_image_s32_2d_0002 Binding 4
OpDecorate %uniform_image_f32_spd_0002 DescriptorSet 2
OpDecorate %uniform_image_f32_spd_0002 Binding 0
OpDecorate %uniform_image_f32_3d_0111 DescriptorSet 2
@@ -222,6 +249,11 @@ OpDecorate %uniform_sampler Binding 0
%uniform_image_f32_2d_0002 = OpVariable %ptr_image_f32_2d_0002 UniformConstant
%type_sampled_image_f32_2d_0002 = OpTypeSampledImage %type_image_f32_2d_0002
%type_image_s32_2d_0002 = OpTypeImage %s32 2D 0 0 0 2 Unknown
%ptr_image_s32_2d_0002 = OpTypePointer UniformConstant %type_image_s32_2d_0002
%uniform_image_s32_2d_0002 = OpVariable %ptr_image_s32_2d_0002 UniformConstant
%type_sampled_image_s32_2d_0002 = OpTypeSampledImage %type_image_s32_2d_0002
%type_image_f32_spd_0002 = OpTypeImage %f32 SubpassData 0 0 0 2 Unknown
%ptr_image_f32_spd_0002 = OpTypePointer UniformConstant %type_image_f32_spd_0002
%uniform_image_f32_spd_0002 = OpVariable %ptr_image_f32_spd_0002 UniformConstant
@@ -703,7 +735,7 @@ TEST_F(ValidateImage, ImageTexelPointerImageNotResultTypePointer) {
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 136[%136] cannot be a "
EXPECT_THAT(getDiagnosticString(), HasSubstr("Operand 140[%140] cannot be a "
"type"));
}
@@ -4571,6 +4603,150 @@ TEST_F(ValidateImage, Issue2463NoSegFault) {
"for OpReturnValue"));
}
TEST_F(ValidateImage, SignExtendV13Bad) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
)";
EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment",
SPV_ENV_UNIVERSAL_1_3)),
HasSubstr("Invalid image operand 'SignExtend'"));
}
TEST_F(ValidateImage, ZeroExtendV13Bad) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
)";
EXPECT_THAT(CompileFailure(GenerateShaderCode(body, "", "Fragment",
SPV_ENV_UNIVERSAL_1_3)),
HasSubstr("Invalid image operand 'ZeroExtend'"));
}
TEST_F(ValidateImage, SignExtendScalarUIntTexelV14Good) {
// Unsigned int sampled type
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32 %img %u32vec2_01 SignExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, SignExtendScalarSIntTexelV14Good) {
// Signed int sampled type
const std::string body = R"(
%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
%res1 = OpImageRead %s32 %img %u32vec2_01 SignExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, SignExtendScalarVectorUIntTexelV14Good) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32vec4 %img %u32vec2_01 SignExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, SignExtendVectorSIntTexelV14Good) {
const std::string body = R"(
%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
%res1 = OpImageRead %s32vec4 %img %u32vec2_01 SignExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
// No negative tests for SignExtend since we don't truly know the
// texel format.
TEST_F(ValidateImage, ZeroExtendScalarUIntTexelV14Good) {
// Unsigned int sampled type
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32 %img %u32vec2_01 ZeroExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, ZeroExtendScalarSIntTexelV14Good) {
// Zeroed int sampled type
const std::string body = R"(
%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
%res1 = OpImageRead %s32 %img %u32vec2_01 ZeroExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, ZeroExtendScalarVectorUIntTexelV14Good) {
const std::string body = R"(
%img = OpLoad %type_image_u32_2d_0000 %uniform_image_u32_2d_0000
%res1 = OpImageRead %u32vec4 %img %u32vec2_01 ZeroExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateImage, ZeroExtendVectorSIntTexelV14Good) {
const std::string body = R"(
%img = OpLoad %type_image_s32_2d_0002 %uniform_image_s32_2d_0002
%res1 = OpImageRead %s32vec4 %img %u32vec2_01 ZeroExtend
)";
const std::string extra = "\nOpCapability StorageImageReadWithoutFormat\n";
CompileSuccessfully(
GenerateShaderCode(body, extra, "Fragment", SPV_ENV_UNIVERSAL_1_4),
SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
// No negative tests for ZeroExtend since we don't truly know the
// texel format.
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -48,8 +48,9 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
HasSubstr(
"Interface variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, EntryPointMissingOutput) {
@@ -74,8 +75,9 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Output variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
HasSubstr(
"Interface variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, InterfaceMissingUseInSubfunction) {
@@ -105,8 +107,9 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Input variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
HasSubstr(
"Interface variable id <5> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, TwoEntryPointsOneFunction) {
@@ -132,8 +135,9 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Input variable id <2> is used by entry point 'func2' id <1>, "
"but is not listed as an interface"));
HasSubstr(
"Interface variable id <2> is used by entry point 'func2' id <1>, "
"but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, MissingInterfaceThroughInitializer) {
@@ -160,8 +164,239 @@ OpFunctionEnd
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Input variable id <6> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
HasSubstr(
"Interface variable id <6> is used by entry point 'func' id <1>, "
"but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p3) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var %var
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer Input %struct
%var = OpVariable %ptr_struct Input
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
TEST_F(ValidateInterfacesTest, NonUniqueInterfacesSPV1p4) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var %var
OpExecutionMode %main LocalSize 1 1 1
OpName %main "main"
OpName %var "var"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer Input %struct
%var = OpVariable %ptr_struct Input
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Non-unique OpEntryPoint interface 2[%var] is disallowed"));
}
TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p3) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer StorageBuffer %struct
%var = OpVariable %ptr_struct StorageBuffer
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %struct %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
TEST_F(ValidateInterfacesTest, MissingGlobalVarSPV1p4) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer StorageBuffer %struct
%var = OpVariable %ptr_struct StorageBuffer
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %struct %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Interface variable id <2> is used by entry point "
"'main' id <1>, but is not listed as an interface"));
}
TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p3) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer Function %struct
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
%var = OpVariable %ptr_struct Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpEntryPoint interfaces must be OpVariables with "
"Storage Class of Input(1) or Output(3). Found Storage "
"Class 7 for Entry Point id 1."));
}
TEST_F(ValidateInterfacesTest, FunctionInterfaceVarSPV1p4) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer Function %struct
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
%var = OpVariable %ptr_struct Function
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("OpEntryPoint interfaces should only list global variables"));
}
TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_NotAllUsedGlobals) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpName %var "var"
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint3 = OpTypeVector %uint 3
%struct = OpTypeStruct %uint3
%ptr_struct = OpTypePointer StorageBuffer %struct
%var = OpVariable %ptr_struct StorageBuffer
%func_ty = OpTypeFunction %void
%main = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %struct %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateInterfacesTest, ModuleSPV1p3ValidateSPV1p4_DuplicateInterface) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %gid %gid
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %gid BuiltIn GlobalInvocationId
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int3 = OpTypeVector %int 3
%ptr_input_int3 = OpTypePointer Input %int3
%gid = OpVariable %ptr_input_int3 Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateInterfacesTest, SPV14MultipleEntryPointsSameFunction) {
const std::string text = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main1" %gid
OpEntryPoint GLCompute %main "main2" %gid
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %gid BuiltIn GlobalInvocationId
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int3 = OpTypeVector %int 3
%ptr_input_int3 = OpTypePointer Input %int3
%gid = OpVariable %ptr_input_int3 Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
} // namespace

View File

@@ -754,13 +754,17 @@ TEST_F(ValidateLimits, ControlFlowNoEntryToLoopGood) {
OpName %loop "loop"
OpName %exit "exit"
%voidt = OpTypeVoid
%boolt = OpTypeBool
%undef = OpUndef %boolt
%funct = OpTypeFunction %voidt
%main = OpFunction %voidt None %funct
%entry = OpLabel
OpBranch %exit
%loop = OpLabel
OpLoopMerge %loop %loop None
OpBranch %loop
OpLoopMerge %dead %loop None
OpBranchConditional %undef %loop %loop
%dead = OpLabel
OpUnreachable
%exit = OpLabel
OpReturn
OpFunctionEnd

View File

@@ -85,6 +85,16 @@ TEST_F(ValidateLiterals, LiteralsShaderGood) {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateLiterals, InvalidInt) {
std::string str = GenerateShaderCode() + R"(
%11 = OpTypeInt 32 90
)";
CompileSuccessfully(str);
EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpTypeInt has invalid signedness:"));
}
TEST_P(ValidateLiteralsShader, LiteralsShaderBad) {
std::string str = GenerateShaderCode() + GetParam();
std::string inst_id = "11";

View File

@@ -24,6 +24,7 @@ namespace spvtools {
namespace val {
namespace {
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Not;
@@ -144,6 +145,18 @@ OpExecutionMode %main OriginUpperLeft
%boolvec3_tft = OpConstantComposite %boolvec3 %true %false %true
%boolvec4_tftf = OpConstantComposite %boolvec4 %true %false %true %false
%arr_u32_2 = OpTypeArray %u32 %u32_2
%st_u32_u32 = OpTypeStruct %u32 %u32
%mat_f32_2_2 = OpTypeMatrix %f32vec2 2
%nul_arr_u32_2 = OpConstantNull %arr_u32_2
%nul_st_u32_u32 = OpConstantNull %st_u32_u32
%nul_mat_f32_2_2 = OpConstantNull %mat_f32_2_2
%arr_u32_2_1_2 = OpConstantComposite %arr_u32_2 %u32_1 %u32_2
%st_u32_u32_1_2 = OpConstantComposite %st_u32_u32 %u32_1 %u32_2
%mat_f32_2_2_01_12 = OpConstantComposite %mat_f32_2_2 %f32vec2_01 %f32vec2_12
%f32vec4ptr = OpTypePointer Function %f32vec4
%main = OpFunction %void None %func
@@ -585,6 +598,20 @@ TEST_F(ValidateLogicals, OpSelectWrongTypeId) {
HasSubstr("Expected scalar or vector type as Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectWrongTypeIdV14) {
// In 1.4, the message changes to allow composites.
const std::string body = R"(
%val1 = OpSelect %void %true %u32_0 %u32_1
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Expected scalar or composite type as Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectPointerNoCapability) {
const std::string body = R"(
%x = OpVariable %f32vec4ptr Function
@@ -687,6 +714,111 @@ TEST_F(ValidateLogicals, OpSelectWrongRightObject) {
HasSubstr("Expected both objects to be of Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectArrayV13Bad) {
const std::string body = R"(
%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectArrayV13TargetV14Bad) {
const std::string body = R"(
%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type"));
}
TEST_F(ValidateLogicals, OpSelectArrayV14Good) {
const std::string body = R"(
%val1 = OpSelect %arr_u32_2 %true %nul_arr_u32_2 %arr_u32_2_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateLogicals, OpSelectStructV13Bad) {
const std::string body = R"(
%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectStructV13TargetV14Bad) {
const std::string body = R"(
%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type"));
}
TEST_F(ValidateLogicals, OpSelectStructV14Good) {
const std::string body = R"(
%val1 = OpSelect %st_u32_u32 %true %nul_st_u32_u32 %st_u32_u32_1_2
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateLogicals, OpSelectMatrixV13Bad) {
const std::string body = R"(
%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type: Select"));
}
TEST_F(ValidateLogicals, OpSelectMatrixV13TargetV14Bad) {
const std::string body = R"(
%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
)";
CompileSuccessfully(GenerateShaderCode(body).c_str());
ASSERT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected scalar or vector type as Result Type"));
}
TEST_F(ValidateLogicals, OpSelectMatrixV14Good) {
const std::string body = R"(
%val1 = OpSelect %mat_f32_2_2 %true %nul_mat_f32_2_2 %mat_f32_2_2_01_12
)";
CompileSuccessfully(GenerateShaderCode(body).c_str(), SPV_ENV_UNIVERSAL_1_4);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateLogicals, OpIEqualSuccess) {
const std::string body = R"(
%val1 = OpIEqual %bool %u32_0 %s32_1
@@ -949,6 +1081,84 @@ OpFunctionEnd
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateLogicals, SelectVectorsScalarCondition) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%int4 = OpTypeVector %int 4
%int4_0 = OpConstantNull %int4
%true = OpConstantTrue %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%select = OpSelect %int4 %true %int4_0 %int4_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected vector sizes of Result Type and the "
"condition to be equal: Select"));
}
TEST_F(ValidateLogicals, SelectVectorsScalarCondition1p4) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%int4 = OpTypeVector %int 4
%int4_0 = OpConstantNull %int4
%true = OpConstantTrue %bool
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%select = OpSelect %int4 %true %int4_0 %int4_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_F(ValidateLogicals, SelectVectorsVectorConditionMismatchedDimensions1p4) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%bool3 = OpTypeVector %bool 3
%int = OpTypeInt 32 0
%int4 = OpTypeVector %int 4
%int4_0 = OpConstantNull %int4
%bool3_null = OpConstantNull %bool3
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%1 = OpLabel
%select = OpSelect %int4 %bool3_null %int4_0 %int4_0
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Expected vector sizes of Result Type and the "
"condition to be equal: Select"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -27,6 +27,7 @@ namespace {
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Values;
using ValidateMemory = spvtest::ValidateBase<bool>;
@@ -1342,32 +1343,6 @@ OpFunctionEnd
"VulkanMemoryModelDeviceScopeKHR capability"));
}
TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood1) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
const std::string spirv = R"(
OpCapability Shader
@@ -1422,6 +1397,138 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}
TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisBadBinaryV13) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
MakePointerAvailableKHR|NonPrivatePointerKHR %device
MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"with two memory access operands requires SPIR-V 1.4 or later"));
}
TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
MakePointerAvailableKHR|NonPrivatePointerKHR %device
MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessFirstWithAvBad) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
MakePointerAvailableKHR|NonPrivatePointerKHR %device
MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Source memory access must not include MakePointerAvailableKHR\n"
" OpCopyMemory %5 %6 MakePointerAvailableKHR|NonPrivatePointerKHR"
" %uint_1 MakePointerAvailableKHR|NonPrivatePointerKHR %uint_1"));
}
TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessSecondWithVisBad) {
const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
MakePointerVisibleKHR|NonPrivatePointerKHR %device
MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Target memory access must not include MakePointerVisibleKHR\n"
" OpCopyMemory %5 %6 MakePointerVisibleKHR|NonPrivatePointerKHR"
" %uint_1 MakePointerVisibleKHR|NonPrivatePointerKHR %uint_1"));
}
TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
const std::string spirv = R"(
OpCapability Shader
@@ -1767,8 +1874,8 @@ OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body.c_str());
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
CompileSuccessfully(body);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("PhysicalStorageBufferEXT must not be used with OpVariable"));
@@ -2934,6 +3041,508 @@ OpFunctionEnd
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}
TEST_F(ValidateMemory, CopyMemoryNoAccessGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateMemory, CopyMemorySimpleMixedAccessGood) {
// Test one memory access operand using features that don't require the
// Vulkan memory model.
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile|Aligned|Nontemporal 4
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV13Bad) {
// Two memory access operands is invalid up to SPIR-V 1.3
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile Volatile
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("CopyMemory with two memory access operands requires "
"SPIR-V 1.4 or later"));
}
TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV14Good) {
// Two memory access operands is valid in SPIR-V 1.4
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile Volatile
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateMemory, CopyMemorySizedNoAccessGood) {
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
TEST_F(ValidateMemory, CopyMemorySizedSimpleMixedAccessGood) {
// Test one memory access operand using features that don't require the
// Vulkan memory model.
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile|Aligned|Nontemporal 4
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV13Bad) {
// Two memory access operands is invalid up to SPIR-V 1.3
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
EXPECT_EQ(SPV_ERROR_INVALID_DATA,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("CopyMemorySized with two memory access operands requires "
"SPIR-V 1.4 or later"));
}
TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV14Good) {
// Two memory access operands is valid in SPIR-V 1.4
const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(), Eq(""));
}
using ValidatePointerComparisons = spvtest::ValidateBase<std::string>;
TEST_P(ValidatePointerComparisons, Good) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int 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, GoodWorkgroup) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_int 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"(%var %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}
TEST_P(ValidatePointerComparisons, BadResultType) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %bool ";
} else {
spirv += " %int ";
}
spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
if (operation == "OpPtrDiff") {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result Type must be an integer scalar"));
} else {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Result Type must be OpTypeBool"));
}
}
TEST_P(ValidatePointerComparisons, BadCapabilities) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int 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);
if (operation == "OpPtrDiff") {
// Gets caught by the grammar.
EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID,
ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Instruction cannot be used without a variable "
"pointers capability"));
}
}
TEST_P(ValidatePointerComparisons, BadOperandType) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %int %var
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%ld %ld
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Operand type must be a pointer"));
}
TEST_P(ValidatePointerComparisons, BadStorageClassWorkgroup) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_int 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"(%var %var
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Workgroup storage class pointer requires "
"VariablePointers capability to be specified"));
}
TEST_P(ValidatePointerComparisons, BadStorageClass) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Private %int
%var = OpVariable %ptr_int Private
%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_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid pointer storage class"));
}
TEST_P(ValidatePointerComparisons, BadDiffOperandTypes) {
const std::string operation = GetParam();
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Private %int
%var = OpVariable %ptr_int Private
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %int %var
%equal = )" + operation;
if (operation == "OpPtrDiff") {
spirv += " %int ";
} else {
spirv += " %bool ";
}
spirv += R"(%var %ld
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
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"));
}
INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons,
Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff"));
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2019 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.
// Validation tests for OpenCL env specific checks
#include <string>
#include "gmock/gmock.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
using testing::HasSubstr;
using ValidateOpenCL = spvtest::ValidateBase<bool>;
TEST_F(ValidateOpenCL, NonPhysicalAddressingModelBad) {
std::string spirv = R"(
OpCapability Kernel
OpMemoryModel Logical OpenCL
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Addressing model must be Physical32 or Physical64 "
"in the OpenCL environment.\n OpMemoryModel Logical "
"OpenCL\n"));
}
TEST_F(ValidateOpenCL, NonOpenCLMemoryModelBad) {
std::string spirv = R"(
OpCapability Kernel
OpMemoryModel Physical32 GLSL450
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_OPENCL_1_2));
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Memory model must be OpenCL in the OpenCL environment."
"\n OpMemoryModel Physical32 GLSL450\n"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@@ -213,7 +213,7 @@ TEST_F(ValidateStorage, RelaxedLogicalPointerFunctionParam) {
OpFunctionEnd
)";
CompileSuccessfully(str);
getValidatorOptions()->relax_logical_pointer = true;
getValidatorOptions()->before_hlsl_legalization = true;
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

Some files were not shown because too many files have changed in this diff Show More