From 30a86bf53821acdce98f49468e169941888f009a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D1=80=D0=B0=D0=BD=D0=B8=D0=BC=D0=B8=D1=80=20=D0=9A?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D1=9F=D0=B8=D1=9B?= Date: Fri, 9 Aug 2019 20:32:29 -0700 Subject: [PATCH] Updated spirv-tools. --- 3rdparty/spirv-tools/Android.mk | 2 + 3rdparty/spirv-tools/BUILD.gn | 4 + 3rdparty/spirv-tools/CHANGES | 71 +- 3rdparty/spirv-tools/CMakeLists.txt | 15 + .../include/generated/build-version.inc | 2 +- .../include/spirv-tools/optimizer.hpp | 32 + .../spirv-tools/source/fuzz/CMakeLists.txt | 8 + .../source/fuzz/data_descriptor.cpp | 42 + .../spirv-tools/source/fuzz/data_descriptor.h | 39 + .../spirv-tools/source/fuzz/fact_manager.cpp | 66 +- .../spirv-tools/source/fuzz/fact_manager.h | 33 +- 3rdparty/spirv-tools/source/fuzz/fuzzer.cpp | 4 + .../source/fuzz/fuzzer_context.cpp | 2 + .../spirv-tools/source/fuzz/fuzzer_context.h | 4 + .../fuzz/fuzzer_pass_add_dead_breaks.cpp | 17 +- .../fuzz/fuzzer_pass_add_dead_continues.cpp | 60 + .../fuzz/fuzzer_pass_add_dead_continues.h | 39 + .../spirv-tools/source/fuzz/fuzzer_util.cpp | 175 +++ .../spirv-tools/source/fuzz/fuzzer_util.h | 44 + .../source/fuzz/protobufs/spvtoolsfuzz.proto | 86 +- .../source/fuzz/transformation.cpp | 4 + .../fuzz/transformation_add_dead_break.cpp | 163 +- .../fuzz/transformation_add_dead_break.h | 31 +- .../fuzz/transformation_add_dead_continue.cpp | 133 ++ .../fuzz/transformation_add_dead_continue.h | 68 + .../fuzz/transformation_copy_object.cpp | 158 ++ .../source/fuzz/transformation_copy_object.h | 68 + .../fuzz/transformation_split_block.cpp | 185 +-- .../source/fuzz/transformation_split_block.h | 16 +- .../uniform_buffer_element_descriptor.cpp | 2 +- .../fuzz/uniform_buffer_element_descriptor.h | 5 +- .../spirv-tools/source/opt/CMakeLists.txt | 4 + .../opt/aggressive_dead_code_elim_pass.cpp | 10 + .../source/opt/const_folding_rules.cpp | 3 + 3rdparty/spirv-tools/source/opt/constants.cpp | 41 +- 3rdparty/spirv-tools/source/opt/constants.h | 10 +- 3rdparty/spirv-tools/source/opt/desc_sroa.cpp | 255 ++++ 3rdparty/spirv-tools/source/opt/desc_sroa.h | 84 ++ .../opt/graphics_robust_access_pass.cpp | 968 ++++++++++++ .../source/opt/graphics_robust_access_pass.h | 146 ++ 3rdparty/spirv-tools/source/opt/instruction.h | 2 +- .../source/opt/instrument_pass.cpp | 32 +- .../spirv-tools/source/opt/instrument_pass.h | 11 +- .../spirv-tools/source/opt/ir_context.cpp | 56 +- 3rdparty/spirv-tools/source/opt/ir_context.h | 13 + .../opt/local_single_block_elim_pass.cpp | 1 + .../opt/local_single_store_elim_pass.cpp | 1 + .../source/opt/local_ssa_elim_pass.cpp | 1 + .../source/opt/merge_return_pass.cpp | 12 +- 3rdparty/spirv-tools/source/opt/optimizer.cpp | 17 +- 3rdparty/spirv-tools/source/opt/passes.h | 2 + .../source/opt/scalar_replacement_pass.cpp | 147 +- .../source/opt/scalar_replacement_pass.h | 19 +- .../source/opt/simplification_pass.cpp | 23 +- 3rdparty/spirv-tools/source/opt/types.cpp | 6 +- 3rdparty/spirv-tools/source/opt/types.h | 6 +- .../spirv-tools/source/util/string_utils.h | 44 + .../source/val/validate_memory_semantics.cpp | 71 +- .../source/val/validate_scopes.cpp | 42 +- .../test/assembly_context_test.cpp | 6 +- .../spirv-tools/test/binary_parse_test.cpp | 3 +- 3rdparty/spirv-tools/test/comment_test.cpp | 3 +- .../test/ext_inst.debuginfo_test.cpp | 3 +- .../spirv-tools/test/ext_inst.opencl_test.cpp | 3 +- 3rdparty/spirv-tools/test/fuzz/CMakeLists.txt | 2 + .../spirv-tools/test/fuzz/fuzz_test_util.cpp | 4 + .../spirv-tools/test/fuzz/fuzz_test_util.h | 29 + .../test/fuzz/fuzzer_replayer_test.cpp | 13 +- .../test/fuzz/fuzzer_shrinker_test.cpp | 109 +- .../transformation_add_dead_continue_test.cpp | 1034 +++++++++++++ .../fuzz/transformation_copy_object_test.cpp | 539 +++++++ 3rdparty/spirv-tools/test/opt/CMakeLists.txt | 3 + .../opt/aggressive_dead_code_elim_test.cpp | 146 ++ .../spirv-tools/test/opt/constants_test.cpp | 167 +++ .../test/opt/decoration_manager_test.cpp | 3 +- .../spirv-tools/test/opt/desc_sroa_test.cpp | 209 +++ 3rdparty/spirv-tools/test/opt/fold_test.cpp | 15 +- .../test/opt/graphics_robust_access_test.cpp | 1307 +++++++++++++++++ .../test/opt/inst_bindless_check_test.cpp | 276 ++++ 3rdparty/spirv-tools/test/opt/pass_fixture.h | 26 + .../test/opt/pass_merge_return_test.cpp | 45 + .../test/opt/scalar_replacement_test.cpp | 147 ++ .../test/opt/simplification_test.cpp | 46 + .../test/text_to_binary.annotation_test.cpp | 3 +- .../test/text_to_binary.debug_test.cpp | 3 +- .../test/text_to_binary.extension_test.cpp | 3 +- .../test/text_to_binary.mode_setting_test.cpp | 3 +- 3rdparty/spirv-tools/test/unit_spirv.cpp | 3 +- 3rdparty/spirv-tools/test/unit_spirv.h | 23 - .../spirv-tools/test/val/val_atomics_test.cpp | 91 +- .../test/val/val_barriers_test.cpp | 104 +- 3rdparty/spirv-tools/tools/fuzz/fuzz.cpp | 9 +- 3rdparty/spirv-tools/tools/opt/opt.cpp | 14 + 93 files changed, 7361 insertions(+), 638 deletions(-) create mode 100644 3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/data_descriptor.h create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp create mode 100644 3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h create mode 100644 3rdparty/spirv-tools/source/opt/desc_sroa.cpp create mode 100644 3rdparty/spirv-tools/source/opt/desc_sroa.h create mode 100644 3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp create mode 100644 3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h create mode 100644 3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp create mode 100644 3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp create mode 100644 3rdparty/spirv-tools/test/opt/constants_test.cpp create mode 100644 3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp create mode 100644 3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp diff --git a/3rdparty/spirv-tools/Android.mk b/3rdparty/spirv-tools/Android.mk index 82d977663..a6278af0d 100644 --- a/3rdparty/spirv-tools/Android.mk +++ b/3rdparty/spirv-tools/Android.mk @@ -95,6 +95,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/decompose_initialized_variables_pass.cpp \ source/opt/decoration_manager.cpp \ source/opt/def_use_manager.cpp \ + source/opt/desc_sroa.cpp \ source/opt/dominator_analysis.cpp \ source/opt/dominator_tree.cpp \ source/opt/eliminate_dead_constant_pass.cpp \ @@ -110,6 +111,7 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/freeze_spec_constant_value_pass.cpp \ source/opt/function.cpp \ source/opt/generate_webgpu_initializers_pass.cpp \ + source/opt/graphics_robust_access_pass.cpp \ source/opt/if_conversion.cpp \ source/opt/inline_pass.cpp \ source/opt/inline_exhaustive_pass.cpp \ diff --git a/3rdparty/spirv-tools/BUILD.gn b/3rdparty/spirv-tools/BUILD.gn index 70772b93a..84b21e13b 100644 --- a/3rdparty/spirv-tools/BUILD.gn +++ b/3rdparty/spirv-tools/BUILD.gn @@ -491,6 +491,8 @@ static_library("spvtools_opt") { "source/opt/decoration_manager.h", "source/opt/def_use_manager.cpp", "source/opt/def_use_manager.h", + "source/opt/desc_sroa.cpp", + "source/opt/desc_sroa.h", "source/opt/dominator_analysis.cpp", "source/opt/dominator_analysis.h", "source/opt/dominator_tree.cpp", @@ -521,6 +523,8 @@ static_library("spvtools_opt") { "source/opt/function.h", "source/opt/generate_webgpu_initializers_pass.cpp", "source/opt/generate_webgpu_initializers_pass.h", + "source/opt/graphics_robust_access_pass.cpp", + "source/opt/graphics_robust_access_pass.h", "source/opt/if_conversion.cpp", "source/opt/if_conversion.h", "source/opt/inline_exhaustive_pass.cpp", diff --git a/3rdparty/spirv-tools/CHANGES b/3rdparty/spirv-tools/CHANGES index 11ecac54e..fd549fa35 100644 --- a/3rdparty/spirv-tools/CHANGES +++ b/3rdparty/spirv-tools/CHANGES @@ -1,7 +1,74 @@ Revision history for SPIRV-Tools -v2019.4-dev 2019-05-15 - - Start v2019.4-dev +v2019.5-dev 2019-08-08 + - Start v2019.5-dev + +v2019.4 2019-08-08 + - General: + - Memory model support for SPIR-V 1.4 + - Add new spirv-fuzz tool + - Add option for base branch in check_code_format.sh + - Removed MarkV and Stats code. (#2576) + - Instrument: Add version 2 of record formats (#2630) + - Linker: Better type comparison for OpTypeArray and OpTypeForwardPointer (#2580) + - Optimizer + - Bindless Validation: Instrument descriptor-based loads and stores (#2583) + - Better folding for OpSpecConstantOp (#2585, #2614) + - Add in individual flags for Vulkan <-> WebGPU passes (#2615) + - Handle nested breaks from switches. (#2624) + - Optimizer: Handle array type with OpSpecConstantOp length (#2652) + - Perform merge return with single return in loop. (#2714) + - Add —preserve-bindings and —preserve-spec-constants (#2693) + - Remove Common Uniform Elimination Pass (#2731) + - Allow ray tracing shaders in inst bindle check pass. (#2733) + - Add pass to inject code for robust-buffer-access semantics (#2771) + - Treat access chain indexes as signed in SROA (#2776) + - Handle RelaxedPrecision in SROA (#2788) + - Add descriptor array scalar replacement (#2742) + Fixes: + - Handle decorations better in some optimizations (#2716) + - Change the order branches are simplified in dead branch elim (#2728) + - Fix bug in merge return (#2734) + - SSA rewriter: Don't use trivial phis (#2757) + - Record correct dominators in merge return (#2760) + - Process OpDecorateId in ADCE (#2761) + - Fix check for unreachable blocks in merge-return (#2762) + - Handle out-of-bounds scalar replacements. (#2767) + - Don't move debug or decorations when folding (#2772) + - Protect against out-of-bounds references when folding OpCompositeExtract (#2774) + - Validator + - Validate loop merge (#2579) + - Validate construct exits (#2459) + - Validate OpenCL memory and addressing model environment rules (#2589) + - Validate OpenCL environment rules for OpTypeImage (#2606) + - Allow breaks to switch merge from nested construct (#2604) + - Validate OpenCL environment rules for OpImageWrite (#2619) + - Allow arrays of out per-primitive builtins for mesh shaders (#2617) + - Validate OpenCL rules for ImageRead and OpImageSampleExplicitLod (#2643) + - Add validation for SPV_EXT_fragment_shader_interlock (#2650) + - Add builtin validation for SPV_NV_shader_sm_builtins (#2656) + - Add validation for Subgroup builtins (#2637) + - Validate variable initializer type (#2668) + - Disallow stores to UBOs (#2651)A + - Validate Volatile memory semantics bit (#2672) + - Basic validation for Component decorations (#2679) + - Validate that in OpenGL env block variables have Binding (#2685) + - Validate usage of 8- and 16-bit types with only storage capabilities (#2704) + - Add validation for SPV_EXT_demote_to_helper_invocation (#2707) + - Extra small storage validation (#2732) + - For Vulkan, disallow structures containing opaque types (#2546) + - Validate storage class OpenCL environment rules for atomics (#2750) + - Update OpControlBarriers rules for WebGPU (#2769) + - Update OpMemoryBarriers rules for WebGPU (#2775) + - Update WebGPU validation rules of OpAtomic*s (#2777) + Fixes: + - Disallow merge targeting block with OpLoopMerge (#2610) + - Update vloadn and vstoren validation to match the OpenCL Extended + Instruction Set Specification (#2599) + - Update memory scope rules for WebGPU (#2725) + - Allow LOD ops in compute shaders with derivative group execution modes (#2752) + - Reduce + Fixes: v2019.3 2019-05-14 - General: diff --git a/3rdparty/spirv-tools/CMakeLists.txt b/3rdparty/spirv-tools/CMakeLists.txt index 9f24e3863..b95b714a9 100644 --- a/3rdparty/spirv-tools/CMakeLists.txt +++ b/3rdparty/spirv-tools/CMakeLists.txt @@ -235,6 +235,21 @@ endmacro(spvtools_pch) add_subdirectory(external) +# Warning about extra semi-colons. +# +# This is not supported on all compilers/versions. so enabling only +# for clang, since that works for all versions that our bots run. +# +# This is intentionally done after adding the external subdirectory, +# so we don't enforce this flag on our dependencies, some of which do +# not pass it. +# +# If the minimum version of CMake supported is updated to 3.0 or +# later, then check_cxx_compiler_flag could be used instead. +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + add_compile_options("-Wextra-semi") +endif() + add_subdirectory(source) add_subdirectory(tools) diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 16d097b1b..eac7f4217 100644 --- a/3rdparty/spirv-tools/include/generated/build-version.inc +++ b/3rdparty/spirv-tools/include/generated/build-version.inc @@ -1 +1 @@ -"v2019.4-dev", "SPIRV-Tools v2019.4-dev v2019.3-99-g65f49dfc" +"v2019.5-dev", "SPIRV-Tools v2019.5-dev v2019.4-2-g95386f9e" diff --git a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp index 4c668b4e4..d442b97f5 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/optimizer.hpp @@ -763,6 +763,38 @@ Optimizer::PassToken CreateDecomposeInitializedVariablesPass(); // continue-targets to legalize for WebGPU. Optimizer::PassToken CreateSplitInvalidUnreachablePass(); +// Creates a graphics robust access pass. +// +// This pass injects code to clamp indexed accesses to buffers and internal +// arrays, providing guarantees satisfying Vulkan's robustBufferAccess rules. +// +// TODO(dneto): Clamps coordinates and sample index for pointer calculations +// into storage images (OpImageTexelPointer). For an cube array image, it +// assumes the maximum layer count times 6 is at most 0xffffffff. +// +// NOTE: This pass will fail with a message if: +// - The module is not a Shader module. +// - The module declares VariablePointers, VariablePointersStorageBuffer, or +// RuntimeDescriptorArrayEXT capabilities. +// - The module uses an addressing model other than Logical +// - Access chain indices are wider than 64 bits. +// - Access chain index for a struct is not an OpConstant integer or is out +// of range. (The module is already invalid if that is the case.) +// - TODO(dneto): The OpImageTexelPointer coordinate component is not 32-bits +// wide. +Optimizer::PassToken CreateGraphicsRobustAccessPass(); + +// Create descriptor scalar replacement pass. +// This pass replaces every array variable |desc| that has a DescriptorSet and +// Binding decorations with a new variable for each element of the array. +// Suppose |desc| was bound at binding |b|. Then the variable corresponding to +// |desc[i]| will have binding |b+i|. The descriptor set will be the same. It +// is assumed that no other variable already has a binding that will used by one +// of the new variables. If not, the pass will generate invalid Spir-V. All +// accesses to |desc| must be OpAccessChain instructions with a literal index +// for the first index. +Optimizer::PassToken CreateDescriptorScalarReplacementPass(); + } // namespace spvtools #endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_ diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index 75e212515..49ee843ab 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -26,11 +26,13 @@ if(SPIRV_BUILD_FUZZER) ) set(SPIRV_TOOLS_FUZZ_SOURCES + data_descriptor.h fact_manager.h fuzzer.h fuzzer_context.h fuzzer_pass.h fuzzer_pass_add_dead_breaks.h + fuzzer_pass_add_dead_continues.h fuzzer_pass_add_useful_constructs.h fuzzer_pass_obfuscate_constants.h fuzzer_pass_permute_blocks.h @@ -46,10 +48,12 @@ if(SPIRV_BUILD_FUZZER) transformation_add_constant_boolean.h transformation_add_constant_scalar.h transformation_add_dead_break.h + transformation_add_dead_continue.h transformation_add_type_boolean.h transformation_add_type_float.h transformation_add_type_int.h transformation_add_type_pointer.h + transformation_copy_object.h transformation_move_block_down.h transformation_replace_boolean_constant_with_constant_binary.h transformation_replace_constant_with_uniform.h @@ -57,11 +61,13 @@ if(SPIRV_BUILD_FUZZER) uniform_buffer_element_descriptor.h ${CMAKE_CURRENT_BINARY_DIR}/protobufs/spvtoolsfuzz.pb.h + data_descriptor.cpp fact_manager.cpp fuzzer.cpp fuzzer_context.cpp fuzzer_pass.cpp fuzzer_pass_add_dead_breaks.cpp + fuzzer_pass_add_dead_continues.cpp fuzzer_pass_add_useful_constructs.cpp fuzzer_pass_obfuscate_constants.cpp fuzzer_pass_permute_blocks.cpp @@ -76,10 +82,12 @@ if(SPIRV_BUILD_FUZZER) transformation_add_constant_boolean.cpp transformation_add_constant_scalar.cpp transformation_add_dead_break.cpp + transformation_add_dead_continue.cpp transformation_add_type_boolean.cpp transformation_add_type_float.cpp transformation_add_type_int.cpp transformation_add_type_pointer.cpp + transformation_copy_object.cpp transformation_move_block_down.cpp transformation_replace_boolean_constant_with_constant_binary.cpp transformation_replace_constant_with_uniform.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp b/3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp new file mode 100644 index 000000000..9cdb2c536 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/data_descriptor.cpp @@ -0,0 +1,42 @@ +// 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/fuzz/data_descriptor.h" + +#include + +namespace spvtools { +namespace fuzz { + +protobufs::DataDescriptor MakeDataDescriptor(uint32_t object, + std::vector&& indices) { + protobufs::DataDescriptor result; + result.set_object(object); + for (auto index : indices) { + result.add_index(index); + } + return result; +} + +bool DataDescriptorEquals::operator()( + const protobufs::DataDescriptor* first, + const protobufs::DataDescriptor* second) const { + return first->object() == second->object() && + first->index().size() == second->index().size() && + std::equal(first->index().begin(), first->index().end(), + second->index().begin()); +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/data_descriptor.h b/3rdparty/spirv-tools/source/fuzz/data_descriptor.h new file mode 100644 index 000000000..731bd2191 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/data_descriptor.h @@ -0,0 +1,39 @@ +// 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_FUZZ_DATA_DESCRIPTOR_H_ +#define SOURCE_FUZZ_DATA_DESCRIPTOR_H_ + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" + +#include + +namespace spvtools { +namespace fuzz { + +// Factory method to create a data descriptor message from an object id and a +// list of indices. +protobufs::DataDescriptor MakeDataDescriptor(uint32_t object, + std::vector&& indices); + +// Equality function for data descriptors. +struct DataDescriptorEquals { + bool operator()(const protobufs::DataDescriptor* first, + const protobufs::DataDescriptor* second) const; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // #define SOURCE_FUZZ_DATA_DESCRIPTOR_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp index 442ff165b..61daa64b3 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.cpp @@ -68,6 +68,9 @@ std::string ToString(const protobufs::Fact& fact) { } // namespace +//======================= +// Constant uniform facts + // The purpose of this struct is to group the fields and data used to represent // facts about uniform constants. struct FactManager::ConstantUniformFacts { @@ -330,10 +333,44 @@ bool FactManager::ConstantUniformFacts::AddFact( return true; } -FactManager::FactManager() { - uniform_constant_facts_ = MakeUnique(); +// End of uniform constant facts +//============================== + +//============================== +// Id synonym facts + +// The purpose of this struct is to group the fields and data used to represent +// facts about id synonyms. +struct FactManager::IdSynonymFacts { + // See method in FactManager which delegates to this method. + void AddFact(const protobufs::FactIdSynonym& fact); + + // A record of all the synonyms that are available. + std::map> synonyms; + + // The set of keys to the above map; useful if you just want to know which ids + // have synonyms. + std::set ids_with_synonyms; +}; + +void FactManager::IdSynonymFacts::AddFact( + const protobufs::FactIdSynonym& fact) { + if (synonyms.count(fact.id()) == 0) { + assert(ids_with_synonyms.count(fact.id()) == 0); + ids_with_synonyms.insert(fact.id()); + synonyms[fact.id()] = std::vector(); + } + assert(ids_with_synonyms.count(fact.id()) == 1); + synonyms[fact.id()].push_back(fact.data_descriptor()); } +// End of id synonym facts +//============================== + +FactManager::FactManager() + : uniform_constant_facts_(MakeUnique()), + id_synonym_facts_(MakeUnique()) {} + FactManager::~FactManager() = default; void FactManager::AddFacts(const MessageConsumer& message_consumer, @@ -350,13 +387,17 @@ void FactManager::AddFacts(const MessageConsumer& message_consumer, bool FactManager::AddFact(const spvtools::fuzz::protobufs::Fact& fact, spvtools::opt::IRContext* context) { - assert(fact.fact_case() == protobufs::Fact::kConstantUniformFact && - "Right now this is the only fact."); - if (!uniform_constant_facts_->AddFact(fact.constant_uniform_fact(), - context)) { - return false; + switch (fact.fact_case()) { + case protobufs::Fact::kConstantUniformFact: + return uniform_constant_facts_->AddFact(fact.constant_uniform_fact(), + context); + case protobufs::Fact::kIdSynonymFact: + id_synonym_facts_->AddFact(fact.id_synonym_fact()); + return true; + default: + assert(false && "Unknown fact type."); + return false; } - return true; } std::vector FactManager::GetConstantsAvailableFromUniformsForType( @@ -389,5 +430,14 @@ FactManager::GetConstantUniformFactsAndTypes() const { return uniform_constant_facts_->facts_and_type_ids; } +const std::set& FactManager::GetIdsForWhichSynonymsAreKnown() const { + return id_synonym_facts_->ids_with_synonyms; +} + +const std::vector& FactManager::GetSynonymsForId( + uint32_t id) const { + return id_synonym_facts_->synonyms.at(id); +} + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fact_manager.h b/3rdparty/spirv-tools/source/fuzz/fact_manager.h index cb4ac58e1..f6ea2477d 100644 --- a/3rdparty/spirv-tools/source/fuzz/fact_manager.h +++ b/3rdparty/spirv-tools/source/fuzz/fact_manager.h @@ -16,6 +16,7 @@ #define SOURCE_FUZZ_FACT_MANAGER_H_ #include +#include #include #include @@ -51,13 +52,12 @@ class FactManager { // fact manager. bool AddFact(const protobufs::Fact& fact, opt::IRContext* context); - // The fact manager will ultimately be responsible for managing a few distinct - // categories of facts. In principle there could be different fact managers - // for each kind of fact, but in practice providing one 'go to' place for - // facts will be convenient. To keep some separation, the public methods of - // the fact manager should be grouped according to the kind of fact to which - // they relate. At present we only have one kind of fact: facts about - // uniform variables. + // The fact manager is responsible for managing a few distinct categories of + // facts. In principle there could be different fact managers for each kind + // of fact, but in practice providing one 'go to' place for facts is + // convenient. To keep some separation, the public methods of the fact + // manager should be grouped according to the kind of fact to which they + // relate. //============================== // Querying facts about uniform constants @@ -96,6 +96,21 @@ class FactManager { // End of uniform constant facts //============================== + //============================== + // Querying facts about id synonyms + + // Returns every id for which a fact of the form "this id is synonymous + // with this piece of data" is known. + const std::set& GetIdsForWhichSynonymsAreKnown() const; + + // Requires that at least one synonym for |id| is known, and returns the + // sequence of all known synonyms. + const std::vector& GetSynonymsForId( + uint32_t id) const; + + // End of id synonym facts + //============================== + private: // For each distinct kind of fact to be managed, we use a separate opaque // struct type. @@ -104,6 +119,10 @@ class FactManager { // buffer elements. std::unique_ptr uniform_constant_facts_; // Unique pointer to internal data. + + struct IdSynonymFacts; // Opaque struct for holding data about id synonyms. + std::unique_ptr + id_synonym_facts_; // Unique pointer to internal data. }; } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index 0fa645ab8..89250a0f7 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -20,6 +20,7 @@ #include "source/fuzz/fact_manager.h" #include "source/fuzz/fuzzer_context.h" #include "source/fuzz/fuzzer_pass_add_dead_breaks.h" +#include "source/fuzz/fuzzer_pass_add_dead_continues.h" #include "source/fuzz/fuzzer_pass_add_useful_constructs.h" #include "source/fuzz/fuzzer_pass_obfuscate_constants.h" #include "source/fuzz/fuzzer_pass_permute_blocks.h" @@ -112,6 +113,9 @@ Fuzzer::FuzzerResultStatus Fuzzer::Run( FuzzerPassAddDeadBreaks(ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out) .Apply(); + FuzzerPassAddDeadContinues(ir_context.get(), &fact_manager, &fuzzer_context, + transformation_sequence_out) + .Apply(); FuzzerPassObfuscateConstants(ir_context.get(), &fact_manager, &fuzzer_context, transformation_sequence_out) .Apply(); diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp index 9252341ba..c8e771900 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.cpp @@ -25,6 +25,7 @@ namespace { // Keep them in alphabetical order. const uint32_t kDefaultChanceOfAddingDeadBreak = 20; +const uint32_t kDefaultChanceOfAddingDeadContinue = 20; const uint32_t kDefaultChanceOfMovingBlockDown = 25; const uint32_t kDefaultChanceOfObfuscatingConstant = 20; const uint32_t kDefaultChanceOfSplittingBlock = 20; @@ -46,6 +47,7 @@ FuzzerContext::FuzzerContext(RandomGenerator* random_generator, : random_generator_(random_generator), next_fresh_id_(min_fresh_id), chance_of_adding_dead_break_(kDefaultChanceOfAddingDeadBreak), + chance_of_adding_dead_continue_(kDefaultChanceOfAddingDeadContinue), chance_of_moving_block_down_(kDefaultChanceOfMovingBlockDown), chance_of_obfuscating_constant_(kDefaultChanceOfObfuscatingConstant), chance_of_splitting_block_(kDefaultChanceOfSplittingBlock), diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h index 2815bf7dd..3eaefc798 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_context.h @@ -44,6 +44,9 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. uint32_t GetChanceOfAddingDeadBreak() { return chance_of_adding_dead_break_; } + uint32_t GetChanceOfAddingDeadContinue() { + return chance_of_adding_dead_continue_; + } uint32_t GetChanceOfMovingBlockDown() { return chance_of_moving_block_down_; } uint32_t GetChanceOfObfuscatingConstant() { return chance_of_obfuscating_constant_; @@ -66,6 +69,7 @@ class FuzzerContext { // Probabilities associated with applying various transformations. // Keep them in alphabetical order. uint32_t chance_of_adding_dead_break_; + uint32_t chance_of_adding_dead_continue_; uint32_t chance_of_moving_block_down_; uint32_t chance_of_obfuscating_constant_; uint32_t chance_of_splitting_block_; diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp index 62e2a8b89..72cd17bcc 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_breaks.cpp @@ -50,11 +50,9 @@ void FuzzerPassAddDeadBreaks::Apply() { // TODO(afd): right now we completely ignore OpPhi instructions at // merge blocks. This will lead to interesting opportunities being // missed. - std::vector phi_ids; auto candidate_transformation = TransformationAddDeadBreak( block.id(), merge_block_id, - GetFuzzerContext()->GetRandomGenerator()->RandomBool(), - std::move(phi_ids)); + GetFuzzerContext()->GetRandomGenerator()->RandomBool(), {}); if (candidate_transformation.IsApplicable(GetIRContext(), *GetFactManager())) { // Only consider a transformation as a candidate if it is applicable. @@ -84,14 +82,11 @@ void FuzzerPassAddDeadBreaks::Apply() { // Remove the transformation at the chosen index from the sequence. auto transformation = std::move(candidate_transformations[index]); candidate_transformations.erase(candidate_transformations.begin() + index); - // Probabilistically decide whether to try to apply it vs. ignore it. - if (GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() > - GetFuzzerContext()->GetChanceOfAddingDeadBreak()) { - continue; - } - // If the transformation can be applied, apply it and add it to the - // sequence of transformations that have been applied. - if (transformation.IsApplicable(GetIRContext(), *GetFactManager())) { + // Probabilistically decide whether to try to apply it vs. ignore it, in the + // case that it is applicable. + if (transformation.IsApplicable(GetIRContext(), *GetFactManager()) && + GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() > + GetFuzzerContext()->GetChanceOfAddingDeadBreak()) { transformation.Apply(GetIRContext(), GetFactManager()); *GetTransformations()->add_transformation() = transformation.ToMessage(); } diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp new file mode 100644 index 000000000..2156d366d --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.cpp @@ -0,0 +1,60 @@ +// 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/fuzz/fuzzer_pass_add_dead_continues.h" + +#include "source/fuzz/transformation_add_dead_continue.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +FuzzerPassAddDeadContinues::FuzzerPassAddDeadContinues( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations) + : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {} + +FuzzerPassAddDeadContinues::~FuzzerPassAddDeadContinues() = default; + +void FuzzerPassAddDeadContinues::Apply() { + // Consider every block in every function. + for (auto& function : *GetIRContext()->module()) { + for (auto& block : function) { + // Make a transformation to add a dead continue from this node; if the + // node turns out to be inappropriate (e.g. by not being in a loop) the + // precondition for the transformation will fail and it will be ignored. + // + // TODO(afd): right now we completely ignore OpPhi instructions at + // merge blocks. This will lead to interesting opportunities being + // missed. + auto candidate_transformation = TransformationAddDeadContinue( + block.id(), GetFuzzerContext()->GetRandomGenerator()->RandomBool(), + {}); + // Probabilistically decide whether to apply the transformation in the + // case that it is applicable. + if (candidate_transformation.IsApplicable(GetIRContext(), + *GetFactManager()) && + GetFuzzerContext()->GetRandomGenerator()->RandomPercentage() > + GetFuzzerContext()->GetChanceOfAddingDeadContinue()) { + candidate_transformation.Apply(GetIRContext(), GetFactManager()); + *GetTransformations()->add_transformation() = + candidate_transformation.ToMessage(); + } + } + } +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h new file mode 100644 index 000000000..6cadc9765 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_pass_add_dead_continues.h @@ -0,0 +1,39 @@ +// 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_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_ +#define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_ + +#include "source/fuzz/fuzzer_pass.h" + +namespace spvtools { +namespace fuzz { + +// A fuzzer pass for adding dead continue edges to the module. +class FuzzerPassAddDeadContinues : public FuzzerPass { + public: + FuzzerPassAddDeadContinues( + opt::IRContext* ir_context, FactManager* fact_manager, + FuzzerContext* fuzzer_context, + protobufs::TransformationSequence* transformations); + + ~FuzzerPassAddDeadContinues(); + + void Apply() override; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // #define SOURCE_FUZZ_FUZZER_PASS_ADD_DEAD_CONTINUES_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp index 645c121d8..9972e47d5 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.cpp @@ -30,6 +30,181 @@ void UpdateModuleIdBound(opt::IRContext* context, uint32_t id) { std::max(context->module()->id_bound(), id + 1)); } +opt::BasicBlock* MaybeFindBlock(opt::IRContext* context, + uint32_t maybe_block_id) { + auto inst = context->get_def_use_mgr()->GetDef(maybe_block_id); + if (inst == nullptr) { + // No instruction defining this id was found. + return nullptr; + } + if (inst->opcode() != SpvOpLabel) { + // The instruction defining the id is not a label, so it cannot be a block + // id. + return nullptr; + } + return context->cfg()->block(maybe_block_id); +} + +bool PhiIdsOkForNewEdge( + opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to, + const google::protobuf::RepeatedField& phi_ids) { + if (bb_from->IsSuccessor(bb_to)) { + // There is already an edge from |from_block| to |to_block|, so there is + // no need to extend OpPhi instructions. Do not allow phi ids to be + // present. This might turn out to be too strict; perhaps it would be OK + // just to ignore the ids in this case. + return phi_ids.empty(); + } + // The edge would add a previously non-existent edge from |from_block| to + // |to_block|, so we go through the given phi ids and check that they exactly + // match the OpPhi instructions in |to_block|. + uint32_t phi_index = 0; + // An explicit loop, rather than applying a lambda to each OpPhi in |bb_to|, + // makes sense here because we need to increment |phi_index| for each OpPhi + // instruction. + for (auto& inst : *bb_to) { + if (inst.opcode() != SpvOpPhi) { + // The OpPhi instructions all occur at the start of the block; if we find + // a non-OpPhi then we have seen them all. + break; + } + if (phi_index == static_cast(phi_ids.size())) { + // Not enough phi ids have been provided to account for the OpPhi + // instructions. + return false; + } + // Look for an instruction defining the next phi id. + opt::Instruction* phi_extension = + context->get_def_use_mgr()->GetDef(phi_ids[phi_index]); + if (!phi_extension) { + // The id given to extend this OpPhi does not exist. + return false; + } + if (phi_extension->type_id() != inst.type_id()) { + // The instruction given to extend this OpPhi either does not have a type + // or its type does not match that of the OpPhi. + return false; + } + + if (context->get_instr_block(phi_extension)) { + // The instruction defining the phi id has an associated block (i.e., it + // is not a global value). Check whether its definition dominates the + // exit of |from_block|. + auto dominator_analysis = + context->GetDominatorAnalysis(bb_from->GetParent()); + if (!dominator_analysis->Dominates(phi_extension, + bb_from->terminator())) { + // The given id is no good as its definition does not dominate the exit + // of |from_block| + return false; + } + } + phi_index++; + } + // Return false if not all of the ids for extending OpPhi instructions are + // needed. This might turn out to be stricter than necessary; perhaps it would + // be OK just to not use the ids in this case. + return phi_index == static_cast(phi_ids.size()); +} + +void AddUnreachableEdgeAndUpdateOpPhis( + opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to, + bool condition_value, + const google::protobuf::RepeatedField& phi_ids) { + assert(PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) && + "Precondition on phi_ids is not satisfied"); + assert(bb_from->terminator()->opcode() == SpvOpBranch && + "Precondition on terminator of bb_from is not satisfied"); + + // Get the id of the boolean constant to be used as the condition. + opt::analysis::Bool bool_type; + opt::analysis::BoolConstant bool_constant( + context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(), + condition_value); + uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant( + &bool_constant, context->get_type_mgr()->GetId(&bool_type)); + + const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to); + auto successor = bb_from->terminator()->GetSingleWordInOperand(0); + + // Add the dead branch, by turning OpBranch into OpBranchConditional, and + // ordering the targets depending on whether the given boolean corresponds to + // true or false. + bb_from->terminator()->SetOpcode(SpvOpBranchConditional); + bb_from->terminator()->SetInOperands( + {{SPV_OPERAND_TYPE_ID, {bool_id}}, + {SPV_OPERAND_TYPE_ID, {condition_value ? successor : bb_to->id()}}, + {SPV_OPERAND_TYPE_ID, {condition_value ? bb_to->id() : successor}}}); + + // Update OpPhi instructions in the target block if this branch adds a + // previously non-existent edge from source to target. + if (!from_to_edge_already_exists) { + uint32_t phi_index = 0; + for (auto& inst : *bb_to) { + if (inst.opcode() != SpvOpPhi) { + break; + } + assert(phi_index < static_cast(phi_ids.size()) && + "There should be exactly one phi id per OpPhi instruction."); + inst.AddOperand({SPV_OPERAND_TYPE_ID, {phi_ids[phi_index]}}); + inst.AddOperand({SPV_OPERAND_TYPE_ID, {bb_from->id()}}); + phi_index++; + } + assert(phi_index == static_cast(phi_ids.size()) && + "There should be exactly one phi id per OpPhi instruction."); + } +} + +bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id, + uint32_t maybe_loop_header_id) { + // We deem a block to be part of a loop's continue construct if the loop's + // continue target dominates the block. + auto containing_construct_block = context->cfg()->block(maybe_loop_header_id); + if (containing_construct_block->IsLoopHeader()) { + auto continue_target = containing_construct_block->ContinueBlockId(); + if (context->GetDominatorAnalysis(containing_construct_block->GetParent()) + ->Dominates(continue_target, block_id)) { + return true; + } + } + return false; +} + +opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset( + opt::BasicBlock* block, const opt::Instruction* base_inst, + uint32_t offset) { + // The cases where |base_inst| is the block's label, vs. inside the block, + // are dealt with separately. + if (base_inst == block->GetLabelInst()) { + // |base_inst| is the block's label. + if (offset == 0) { + // We cannot return an iterator to the block's label. + return block->end(); + } + // Conceptually, the first instruction in the block is [label + 1]. + // We thus start from 1 when applying the offset. + auto inst_it = block->begin(); + for (uint32_t i = 1; i < offset && inst_it != block->end(); i++) { + ++inst_it; + } + // This is either the desired instruction, or the end of the block. + return inst_it; + } + // |base_inst| is inside the block. + for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) { + if (base_inst == &*inst_it) { + // We have found the base instruction; we now apply the offset. + for (uint32_t i = 0; i < offset && inst_it != block->end(); i++) { + ++inst_it; + } + // This is either the desired instruction, or the end of the block. + return inst_it; + } + } + assert(false && "The base instruction was not found."); + return nullptr; +} + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h index 30d870b4b..47588b043 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer_util.h @@ -15,6 +15,11 @@ #ifndef SOURCE_FUZZ_FUZZER_UTIL_H_ #define SOURCE_FUZZ_FUZZER_UTIL_H_ +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/opt/basic_block.h" +#include "source/opt/instruction.h" #include "source/opt/ir_context.h" namespace spvtools { @@ -30,6 +35,45 @@ bool IsFreshId(opt::IRContext* context, uint32_t id); // account for the given id. void UpdateModuleIdBound(opt::IRContext* context, uint32_t id); +// Return the block with id |maybe_block_id| if it exists, and nullptr +// otherwise. +opt::BasicBlock* MaybeFindBlock(opt::IRContext* context, + uint32_t maybe_block_id); + +// When adding an edge from |bb_from| to |bb_to| (which are assumed to be blocks +// in the same function), it is important to supply |bb_to| with ids that can be +// used to augment OpPhi instructions in the case that there is not already such +// an edge. This function returns true if and only if the ids provided in +// |phi_ids| suffice for this purpose, +bool PhiIdsOkForNewEdge( + opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to, + const google::protobuf::RepeatedField& phi_ids); + +// Requires that PhiIdsOkForNewEdge(context, bb_from, bb_to, phi_ids) holds, +// and that bb_from ends with "OpBranch %some_block". Turns OpBranch into +// "OpBranchConditional |condition_value| ...", such that control will branch +// to %some_block, with |bb_to| being the unreachable alternative. Updates +// OpPhi instructions in |bb_to| using |phi_ids| so that the new edge is valid. +void AddUnreachableEdgeAndUpdateOpPhis( + opt::IRContext* context, opt::BasicBlock* bb_from, opt::BasicBlock* bb_to, + bool condition_value, + const google::protobuf::RepeatedField& phi_ids); + +// Returns true if and only if |maybe_loop_header_id| is a loop header and +// |block_id| is in the continue construct of the associated loop. +bool BlockIsInLoopContinueConstruct(opt::IRContext* context, uint32_t block_id, + uint32_t maybe_loop_header_id); + +// Requires that |base_inst| is either the label instruction of |block| or an +// instruction inside |block|. +// +// If the block contains a (non-label, non-terminator) instruction |offset| +// instructions after |base_inst|, an iterator to this instruction is returned. +// +// Otherwise |block|->end() is returned. +opt::BasicBlock::iterator GetIteratorForBaseInstructionAndOffset( + opt::BasicBlock* block, const opt::Instruction* base_inst, uint32_t offset); + } // namespace fuzzerutil } // namespace fuzz diff --git a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto index aecd4c7a9..4e8dcac3c 100644 --- a/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/3rdparty/spirv-tools/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -57,6 +57,22 @@ message IdUseDescriptor { } +message DataDescriptor { + + // Represents a data element that can be accessed from an id, by walking the + // type hierarchy via a sequence of 0 or more indices. + // + // Very similar to a UniformBufferElementDescriptor, except that a + // DataDescriptor is rooted at the id of a scalar or composite. + + // The object being accessed - a scalar or composite + uint32 object = 1; + + // 0 or more indices, used to index into a composite object + repeated uint32 index = 2; + +} + message UniformBufferElementDescriptor { // Represents a data element inside a uniform buffer. The element is @@ -97,6 +113,7 @@ message Fact { oneof fact { // Order the fact options by numeric id (rather than alphabetically). FactConstantUniform constant_uniform_fact = 1; + FactIdSynonym id_synonym_fact = 2; } } @@ -118,6 +135,22 @@ message FactConstantUniform { } +message FactIdSynonym { + + // Records the fact that the data held in an id is guaranteed to be equal to + // the data held in a data descriptor. spirv-fuzz can use this to replace + // uses of the id with references to the data described by the data + // descriptor. + + // An id + uint32 id = 1; + + // A data descriptor guaranteed to hold a value identical to that held by the + // id + DataDescriptor data_descriptor = 2; + +} + message TransformationSequence { repeated Transformation transformation = 1; } @@ -137,6 +170,8 @@ message Transformation { TransformationReplaceBooleanConstantWithConstantBinary replace_boolean_constant_with_constant_binary = 9; TransformationAddTypePointer add_type_pointer = 10; TransformationReplaceConstantWithUniform replace_constant_with_uniform = 11; + TransformationAddDeadContinue add_dead_continue = 12; + TransformationCopyObject copy_object = 13; // Add additional option using the next available number. } } @@ -190,6 +225,25 @@ message TransformationAddDeadBreak { } +message TransformationAddDeadContinue { + + // A transformation that turns a basic block appearing in a loop and that + // unconditionally branches to its successor into a block that potentially + // branches to the continue target of the loop, but in such a manner that the + // continue branch cannot actually be taken. + + // The block to continue from + uint32 from_block = 1; + + // Determines whether the continue condition is true or false + bool continue_condition_value = 2; + + // A sequence of ids suitable for extending OpPhi instructions as a result of + // the new break edge + repeated uint32 phi_id = 3; + +} + message TransformationAddTypeBoolean { // Adds OpTypeBool to the module @@ -242,6 +296,26 @@ message TransformationAddTypePointer { } +message TransformationCopyObject { + + // A transformation that introduces an OpCopyObject instruction to make a + // copy of an object. + + // Id of the object to be copied + uint32 object = 1; + + // The id of an instruction in a block + uint32 base_instruction_id = 2; + + // An offset, such that OpCopyObject instruction should be inserted right + // before the instruction |offset| instructions after |base_instruction_id| + uint32 offset = 3; + + // A fresh id for the copied object + uint32 fresh_id = 4; + +} + message TransformationMoveBlockDown { // A transformation that moves a basic block to be one position lower in @@ -271,6 +345,7 @@ message TransformationReplaceConstantWithUniform { } message TransformationReplaceBooleanConstantWithConstantBinary { + // A transformation to capture replacing a use of a boolean constant with // binary operation on two constant values @@ -293,13 +368,14 @@ message TransformationReplaceBooleanConstantWithConstantBinary { message TransformationSplitBlock { - // A transformation that splits a basic block into two basic blocks. + // A transformation that splits a basic block into two basic blocks - // The result id of an instruction. - uint32 result_id = 1; + // The result id of an instruction + uint32 base_instruction_id = 1; - // An offset, such that the block containing |result_id_| should be split - // right before the instruction |offset_| instructions after |result_id_|. + // An offset, such that the block containing |base_instruction_id| should be + // split right before the instruction |offset| instructions after + // |base_instruction_id| uint32 offset = 2; // An id that must not yet be used by the module to which this transformation diff --git a/3rdparty/spirv-tools/source/fuzz/transformation.cpp b/3rdparty/spirv-tools/source/fuzz/transformation.cpp index 18a66d41e..a252734dc 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation.cpp @@ -20,6 +20,7 @@ #include "transformation_add_constant_boolean.h" #include "transformation_add_constant_scalar.h" #include "transformation_add_dead_break.h" +#include "transformation_add_dead_continue.h" #include "transformation_add_type_boolean.h" #include "transformation_add_type_float.h" #include "transformation_add_type_int.h" @@ -45,6 +46,9 @@ std::unique_ptr Transformation::FromMessage( message.add_constant_scalar()); case protobufs::Transformation::TransformationCase::kAddDeadBreak: return MakeUnique(message.add_dead_break()); + case protobufs::Transformation::TransformationCase::kAddDeadContinue: + return MakeUnique( + message.add_dead_continue()); case protobufs::Transformation::TransformationCase::kAddTypeBoolean: return MakeUnique( message.add_type_boolean()); diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp index 7b55f474c..229dc9021 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.cpp @@ -15,6 +15,7 @@ #include "source/fuzz/transformation_add_dead_break.h" #include "source/fuzz/fact_manager.h" +#include "source/fuzz/fuzzer_util.h" #include "source/opt/basic_block.h" #include "source/opt/ir_context.h" #include "source/opt/struct_cfg_analysis.h" @@ -37,98 +38,6 @@ TransformationAddDeadBreak::TransformationAddDeadBreak( } } -opt::BasicBlock* TransformationAddDeadBreak::MaybeFindBlock( - opt::IRContext* context, uint32_t maybe_block_id) const { - auto inst = context->get_def_use_mgr()->GetDef(maybe_block_id); - if (inst == nullptr) { - // No instruction defining this id was found. - return nullptr; - } - if (inst->opcode() != SpvOpLabel) { - // The instruction defining the id is not a label, so it cannot be a block - // id. - return nullptr; - } - return context->cfg()->block(maybe_block_id); -} - -bool TransformationAddDeadBreak::PhiIdsOk(opt::IRContext* context, - opt::BasicBlock* bb_from, - opt::BasicBlock* bb_to) const { - if (bb_from->IsSuccessor(bb_to)) { - // There is already an edge from |from_block| to |to_block|, so there is - // no need to extend OpPhi instructions. Do not allow phi ids to be - // present. This might turn out to be too strict; perhaps it would be OK - // just to ignore the ids in this case. - return message_.phi_id().empty(); - } - // The break would add a previously non-existent edge from |from_block| to - // |to_block|, so we go through the given phi ids and check that they exactly - // match the OpPhi instructions in |to_block|. - uint32_t phi_index = 0; - // An explicit loop, rather than applying a lambda to each OpPhi in |bb_to|, - // makes sense here because we need to increment |phi_index| for each OpPhi - // instruction. - for (auto& inst : *bb_to) { - if (inst.opcode() != SpvOpPhi) { - // The OpPhi instructions all occur at the start of the block; if we find - // a non-OpPhi then we have seen them all. - break; - } - if (phi_index == static_cast(message_.phi_id().size())) { - // Not enough phi ids have been provided to account for the OpPhi - // instructions. - return false; - } - // Look for an instruction defining the next phi id. - opt::Instruction* phi_extension = - context->get_def_use_mgr()->GetDef(message_.phi_id()[phi_index]); - if (!phi_extension) { - // The id given to extend this OpPhi does not exist. - return false; - } - if (phi_extension->type_id() != inst.type_id()) { - // The instruction given to extend this OpPhi either does not have a type - // or its type does not match that of the OpPhi. - return false; - } - - if (context->get_instr_block(phi_extension)) { - // The instruction defining the phi id has an associated block (i.e., it - // is not a global value). Check whether its definition dominates the - // exit of |from_block|. - auto dominator_analysis = - context->GetDominatorAnalysis(bb_from->GetParent()); - if (!dominator_analysis->Dominates(phi_extension, - bb_from->terminator())) { - // The given id is no good as its definition does not dominate the exit - // of |from_block| - return false; - } - } - phi_index++; - } - // Reject the transformation if not all of the ids for extending OpPhi - // instructions are needed. This might turn out to be stricter than necessary; - // perhaps it would be OK just to not use the ids in this case. - return phi_index == static_cast(message_.phi_id().size()); -} - -bool TransformationAddDeadBreak::FromBlockIsInLoopContinueConstruct( - opt::IRContext* context, uint32_t maybe_loop_header) const { - // We deem a block to be part of a loop's continue construct if the loop's - // continue target dominates the block. - auto containing_construct_block = context->cfg()->block(maybe_loop_header); - if (containing_construct_block->IsLoopHeader()) { - auto continue_target = containing_construct_block->ContinueBlockId(); - if (context->GetDominatorAnalysis(containing_construct_block->GetParent()) - ->Dominates(continue_target, message_.from_block())) { - return true; - } - } - return false; -} - bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow( opt::IRContext* context, opt::BasicBlock* bb_from) const { // Look at the structured control flow associated with |from_block| and @@ -180,7 +89,8 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow( // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2577): We do not // currently allow a dead break from a back edge block, but we could and // ultimately should. - return !FromBlockIsInLoopContinueConstruct(context, containing_construct); + return !fuzzerutil::BlockIsInLoopContinueConstruct( + context, message_.from_block(), containing_construct); } // Case (3) holds if and only if |to_block| is the merge block for this @@ -191,7 +101,8 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow( if (containing_loop_header && message_.to_block() == context->cfg()->block(containing_loop_header)->MergeBlockId()) { - return !FromBlockIsInLoopContinueConstruct(context, containing_loop_header); + return !fuzzerutil::BlockIsInLoopContinueConstruct( + context, message_.from_block(), containing_loop_header); } return false; } @@ -199,7 +110,7 @@ bool TransformationAddDeadBreak::AddingBreakRespectsStructuredControlFlow( bool TransformationAddDeadBreak::IsApplicable( opt::IRContext* context, const FactManager& /*unused*/) const { // First, we check that a constant with the same value as - // |break_condition_value| is present. + // |message_.break_condition_value| is present. opt::analysis::Bool bool_type; auto registered_bool_type = context->get_type_mgr()->GetRegisteredType(&bool_type); @@ -214,17 +125,20 @@ bool TransformationAddDeadBreak::IsApplicable( return false; } - // Check that |from_block| and |to_block| really are block ids - opt::BasicBlock* bb_from = MaybeFindBlock(context, message_.from_block()); + // Check that |message_.from_block| and |message_.to_block| really are block + // ids + opt::BasicBlock* bb_from = + fuzzerutil::MaybeFindBlock(context, message_.from_block()); if (bb_from == nullptr) { return false; } - opt::BasicBlock* bb_to = MaybeFindBlock(context, message_.to_block()); + opt::BasicBlock* bb_to = + fuzzerutil::MaybeFindBlock(context, message_.to_block()); if (bb_to == nullptr) { return false; } - // Check that |from_block| ends with an unconditional branch. + // Check that |message_.from_block| ends with an unconditional branch. if (bb_from->terminator()->opcode() != SpvOpBranch) { // The block associated with the id does not end with an unconditional // branch. @@ -243,7 +157,8 @@ bool TransformationAddDeadBreak::IsApplicable( "The id of the block we found should match the target id for the break."); // Check whether the data passed to extend OpPhi instructions is appropriate. - if (!PhiIdsOk(context, bb_from, bb_to)) { + if (!fuzzerutil::PhiIdsOkForNewEdge(context, bb_from, bb_to, + message_.phi_id())) { return false; } @@ -254,50 +169,10 @@ bool TransformationAddDeadBreak::IsApplicable( void TransformationAddDeadBreak::Apply(opt::IRContext* context, FactManager* /*unused*/) const { - // Get the id of the boolean constant to be used as the break condition. - opt::analysis::Bool bool_type; - opt::analysis::BoolConstant bool_constant( - context->get_type_mgr()->GetRegisteredType(&bool_type)->AsBool(), - message_.break_condition_value()); - uint32_t bool_id = context->get_constant_mgr()->FindDeclaredConstant( - &bool_constant, context->get_type_mgr()->GetId(&bool_type)); - - auto bb_from = context->cfg()->block(message_.from_block()); - auto bb_to = context->cfg()->block(message_.to_block()); - const bool from_to_edge_already_exists = bb_from->IsSuccessor(bb_to); - auto successor = bb_from->terminator()->GetSingleWordInOperand(0); - assert(bb_from->terminator()->opcode() == SpvOpBranch && - "Precondition for the transformation requires that the source block " - "ends with OpBranch"); - - // Add the dead break, by turning OpBranch into OpBranchConditional, and - // ordering the targets depending on whether the given boolean corresponds to - // true or false. - bb_from->terminator()->SetOpcode(SpvOpBranchConditional); - bb_from->terminator()->SetInOperands( - {{SPV_OPERAND_TYPE_ID, {bool_id}}, - {SPV_OPERAND_TYPE_ID, - {message_.break_condition_value() ? successor : message_.to_block()}}, - {SPV_OPERAND_TYPE_ID, - {message_.break_condition_value() ? message_.to_block() : successor}}}); - - // Update OpPhi instructions in the target block if this break adds a - // previously non-existent edge from source to target. - if (!from_to_edge_already_exists) { - uint32_t phi_index = 0; - for (auto& inst : *bb_to) { - if (inst.opcode() != SpvOpPhi) { - break; - } - assert(phi_index < static_cast(message_.phi_id().size()) && - "There should be exactly one phi id per OpPhi instruction."); - inst.AddOperand({SPV_OPERAND_TYPE_ID, {message_.phi_id()[phi_index]}}); - inst.AddOperand({SPV_OPERAND_TYPE_ID, {message_.from_block()}}); - phi_index++; - } - assert(phi_index == static_cast(message_.phi_id().size()) && - "There should be exactly one phi id per OpPhi instruction."); - } + fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis( + context, context->cfg()->block(message_.from_block()), + context->cfg()->block(message_.to_block()), + message_.break_condition_value(), message_.phi_id()); // Invalidate all analyses context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.h index 23392ba58..aeb4dbb78 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_break.h @@ -34,25 +34,25 @@ class TransformationAddDeadBreak : public Transformation { bool break_condition_value, std::vector phi_id); - // - |message.from_block| must be the id of a block a in the given module. - // - |message.to_block| must be the id of a block b in the given module. - // - if |message.break_condition_value| holds (does not hold) then + // - |message_.from_block| must be the id of a block a in the given module. + // - |message_.to_block| must be the id of a block b in the given module. + // - if |message_.break_condition_value| holds (does not hold) then // OpConstantTrue (OpConstantFalse) must be present in the module - // - |message.phi_ids| must be a list of ids that are all available at - // |message.from_block| + // - |message_.phi_ids| must be a list of ids that are all available at + // |message_.from_block| // - a and b must be in the same function. // - b must be a merge block. // - a must end with an unconditional branch to some block c. // - replacing this branch with a conditional branch to b or c, with - // the boolean constant associated with |message.break_condition_value| as - // the condition, and the ids in |message.phi_ids| used to extend + // the boolean constant associated with |message_.break_condition_value| as + // the condition, and the ids in |message_.phi_ids| used to extend // any OpPhi instructions at b as a result of the edge from a, must // maintain validity of the module. bool IsApplicable(opt::IRContext* context, const FactManager& fact_manager) const override; // Replaces the terminator of a with a conditional branch to b or c. - // The boolean constant associated with |message.break_condition_value| is + // The boolean constant associated with |message_.break_condition_value| is // used as the condition, and the order of b and c is arranged such that // control is guaranteed to jump to c. void Apply(opt::IRContext* context, FactManager* fact_manager) const override; @@ -60,21 +60,6 @@ class TransformationAddDeadBreak : public Transformation { protobufs::Transformation ToMessage() const override; private: - // Return the block with id |maybe_block_id| if it exists, and nullptr - // otherwise. - opt::BasicBlock* MaybeFindBlock(opt::IRContext* context, - uint32_t maybe_block_id) const; - - // Returns true if and only if the phi ids associated with |message_| are - // sufficient to allow an edge from |bb_from| to |bb_to| to be added. - bool PhiIdsOk(opt::IRContext* context, opt::BasicBlock* bb_from, - opt::BasicBlock* bb_to) const; - - // Returns true if and only if |message_.from_block| is in the continue - // construct of a loop headed at |maybe_loop_header|. - bool FromBlockIsInLoopContinueConstruct(opt::IRContext* context, - uint32_t maybe_loop_header) const; - // Returns true if and only if adding an edge from |bb_from| to // |message_.to_block| respects structured control flow. bool AddingBreakRespectsStructuredControlFlow(opt::IRContext* context, diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp new file mode 100644 index 000000000..e3b3da295 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.cpp @@ -0,0 +1,133 @@ +// 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/fuzz/transformation_add_dead_continue.h" + +#include "source/fuzz/fuzzer_util.h" + +namespace spvtools { +namespace fuzz { + +TransformationAddDeadContinue::TransformationAddDeadContinue( + const spvtools::fuzz::protobufs::TransformationAddDeadContinue& message) + : message_(message) {} + +TransformationAddDeadContinue::TransformationAddDeadContinue( + uint32_t from_block, bool continue_condition_value, + std::vector phi_id) { + message_.set_from_block(from_block); + message_.set_continue_condition_value(continue_condition_value); + for (auto id : phi_id) { + message_.add_phi_id(id); + } +} + +bool TransformationAddDeadContinue::IsApplicable( + opt::IRContext* context, const FactManager& /*unused*/) const { + // First, we check that a constant with the same value as + // |message_.continue_condition_value| is present. + opt::analysis::Bool bool_type; + auto registered_bool_type = + context->get_type_mgr()->GetRegisteredType(&bool_type); + if (!registered_bool_type) { + return false; + } + opt::analysis::BoolConstant bool_constant( + registered_bool_type->AsBool(), message_.continue_condition_value()); + if (!context->get_constant_mgr()->FindConstant(&bool_constant)) { + // The required constant is not present, so the transformation cannot be + // applied. + return false; + } + + // Check that |message_.from_block| really is a block id. + opt::BasicBlock* bb_from = + fuzzerutil::MaybeFindBlock(context, message_.from_block()); + if (bb_from == nullptr) { + return false; + } + + // Check that |message_.from_block| ends with an unconditional branch. + if (bb_from->terminator()->opcode() != SpvOpBranch) { + // The block associated with the id does not end with an unconditional + // branch. + return false; + } + + assert(bb_from != nullptr && + "We should have found a block if this line of code is reached."); + assert( + bb_from->id() == message_.from_block() && + "The id of the block we found should match the source id for the break."); + + // Get the header for the innermost loop containing |message_.from_block|. + // Because the structured CFG analysis does not regard a loop header as part + // of the loop it heads, we check first whether bb_from is a loop header + // before using the structured CFG analysis. + auto loop_header = bb_from->IsLoopHeader() + ? message_.from_block() + : context->GetStructuredCFGAnalysis()->ContainingLoop( + message_.from_block()); + if (!loop_header) { + return false; + } + + if (fuzzerutil::BlockIsInLoopContinueConstruct(context, message_.from_block(), + loop_header)) { + // We cannot jump to the continue target from the continue construct. + return false; + } + + auto continue_block = context->cfg()->block(loop_header)->ContinueBlockId(); + + if (context->GetStructuredCFGAnalysis()->IsMergeBlock(continue_block)) { + // A branch straight to the continue target that is also a merge block might + // break the property that a construct header must dominate its merge block + // (if the merge block is reachable). + return false; + } + + // The transformation is good if and only if the given phi ids are sufficient + // to extend relevant OpPhi instructions in the continue block. + return fuzzerutil::PhiIdsOkForNewEdge(context, bb_from, + context->cfg()->block(continue_block), + message_.phi_id()); +} + +void TransformationAddDeadContinue::Apply(opt::IRContext* context, + FactManager* /*unused*/) const { + auto bb_from = context->cfg()->block(message_.from_block()); + auto continue_block = + bb_from->IsLoopHeader() + ? bb_from->ContinueBlockId() + : context->GetStructuredCFGAnalysis()->LoopContinueBlock( + message_.from_block()); + assert(continue_block && + "Precondition for this transformation requires that " + "message_.from_block must be in a loop."); + fuzzerutil::AddUnreachableEdgeAndUpdateOpPhis( + context, bb_from, context->cfg()->block(continue_block), + message_.continue_condition_value(), message_.phi_id()); + // Invalidate all analyses + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); +} + +protobufs::Transformation TransformationAddDeadContinue::ToMessage() const { + protobufs::Transformation result; + *result.mutable_add_dead_continue() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h new file mode 100644 index 000000000..e49e2a8c3 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_add_dead_continue.h @@ -0,0 +1,68 @@ +// 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_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_ +#define SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_ + +#include + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationAddDeadContinue : public Transformation { + public: + explicit TransformationAddDeadContinue( + const protobufs::TransformationAddDeadContinue& message); + + TransformationAddDeadContinue(uint32_t from_block, + bool continue_condition_value, + std::vector phi_id); + + // - |message_.from_block| must be the id of a block a in the given module. + // - a must be contained in a loop with continue target b + // - b must not be the merge block of a selection construct + // - if |message_.continue_condition_value| holds (does not hold) then + // OpConstantTrue (OpConstantFalse) must be present in the module + // - |message_.phi_ids| must be a list of ids that are all available at + // |message_.from_block| + // - a must end with an unconditional branch to some block c. + // - replacing this branch with a conditional branch to b or c, with + // the boolean constant associated with |message_.continue_condition_value| + // as the condition, and the ids in |message_.phi_ids| used to extend any + // OpPhi instructions at b as a result of the edge from a, must maintain + // validity of the module. + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // Replaces the terminator of a with a conditional branch to b or c. + // The boolean constant associated with |message_.continue_condition_value| is + // used as the condition, and the order of b and c is arranged such that + // control is guaranteed to jump to c. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationAddDeadContinue message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_ADD_DEAD_CONTINUE_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp new file mode 100644 index 000000000..f9ead434c --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.cpp @@ -0,0 +1,158 @@ +// 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/fuzz/transformation_copy_object.h" + +#include "source/fuzz/fuzzer_util.h" +#include "source/opt/instruction.h" +#include "source/util/make_unique.h" + +namespace spvtools { +namespace fuzz { + +TransformationCopyObject::TransformationCopyObject( + const protobufs::TransformationCopyObject& message) + : message_(message) {} + +TransformationCopyObject::TransformationCopyObject(uint32_t object, + uint32_t base_instruction_id, + uint32_t offset, + uint32_t fresh_id) { + message_.set_object(object); + message_.set_base_instruction_id(base_instruction_id); + message_.set_offset(offset); + message_.set_fresh_id(fresh_id); +} + +bool TransformationCopyObject::IsApplicable( + opt::IRContext* context, const FactManager& /*fact_manager*/) const { + if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { + // We require the id for the object copy to be unused. + return false; + } + // The id of the object to be copied must exist + auto object_inst = context->get_def_use_mgr()->GetDef(message_.object()); + if (!object_inst) { + return false; + } + if (!object_inst->type_id()) { + // We can only apply OpCopyObject to instructions that have types. + return false; + } + if (!context->get_decoration_mgr() + ->GetDecorationsFor(message_.object(), true) + .empty()) { + // We do not copy objects that have decorations: if the copy is not + // decorated analogously, using the original object vs. its copy may not be + // equivalent. + // TODO(afd): it would be possible to make the copy but not add an id + // synonym. + return false; + } + + auto base_instruction = + context->get_def_use_mgr()->GetDef(message_.base_instruction_id()); + if (!base_instruction) { + // The given id to insert after is not defined. + return false; + } + + auto destination_block = context->get_instr_block(base_instruction); + if (!destination_block) { + // The given id to insert after is not in a block. + return false; + } + + auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset( + destination_block, base_instruction, message_.offset()); + + if (insert_before == destination_block->end()) { + // The offset was inappropriate. + return false; + } + if (insert_before->PreviousNode() && + (insert_before->PreviousNode()->opcode() == SpvOpLoopMerge || + insert_before->PreviousNode()->opcode() == SpvOpSelectionMerge)) { + // We cannot insert a copy directly after a merge instruction. + return false; + } + if (insert_before->opcode() == SpvOpVariable) { + // We cannot insert a copy directly before a variable; variables in a + // function must be contiguous in the entry block. + return false; + } + // We cannot insert a copy directly before OpPhi, because OpPhi instructions + // need to be contiguous at the start of a block. + if (insert_before->opcode() == SpvOpPhi) { + return false; + } + // |message_object| must be available at the point where we want to add the + // copy. It is available if it is at global scope (in which case it has no + // block), or if it dominates the point of insertion but is different from the + // point of insertion. + // + // The reason why the object needs to be different from the insertion point is + // that the copy will be added *before* this point, and we do not want to + // insert it before the object's defining instruction. + return !context->get_instr_block(object_inst) || + (object_inst != &*insert_before && + context->GetDominatorAnalysis(destination_block->GetParent()) + ->Dominates(object_inst, &*insert_before)); +} + +void TransformationCopyObject::Apply(opt::IRContext* context, + FactManager* fact_manager) const { + // - A new instruction, + // %|message_.fresh_id| = OpCopyObject %ty %|message_.object| + // is added directly before the instruction at |message_.insert_after_id| + + // |message_|.offset, where %ty is the type of |message_.object|. + // - The fact that |message_.fresh_id| and |message_.object| are synonyms + // is added to the fact manager. + // The id of the object to be copied must exist + auto object_inst = context->get_def_use_mgr()->GetDef(message_.object()); + assert(object_inst && "The object to be copied must exist."); + auto base_instruction = + context->get_def_use_mgr()->GetDef(message_.base_instruction_id()); + assert(base_instruction && "The base instruction must exist."); + auto destination_block = context->get_instr_block(base_instruction); + assert(destination_block && "The base instruction must be in a block."); + auto insert_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset( + destination_block, base_instruction, message_.offset()); + assert(insert_before != destination_block->end() && + "There must be an instruction before which the copy can be inserted."); + + opt::Instruction::OperandList operands = { + {SPV_OPERAND_TYPE_ID, {message_.object()}}}; + insert_before->InsertBefore(MakeUnique( + context, SpvOp::SpvOpCopyObject, object_inst->type_id(), + message_.fresh_id(), operands)); + + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); + + protobufs::Fact fact; + fact.mutable_id_synonym_fact()->set_id(message_.object()); + fact.mutable_id_synonym_fact()->mutable_data_descriptor()->set_object( + message_.fresh_id()); + fact_manager->AddFact(fact, context); +} + +protobufs::Transformation TransformationCopyObject::ToMessage() const { + protobufs::Transformation result; + *result.mutable_copy_object() = message_; + return result; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h new file mode 100644 index 000000000..6ce72df4e --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/transformation_copy_object.h @@ -0,0 +1,68 @@ +// 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_FUZZ_TRANSFORMATION_COPY_OBJECT_H_ +#define SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_ + +#include "source/fuzz/fact_manager.h" +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "source/fuzz/transformation.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace fuzz { + +class TransformationCopyObject : public Transformation { + public: + explicit TransformationCopyObject( + const protobufs::TransformationCopyObject& message); + + TransformationCopyObject(uint32_t fresh_id, uint32_t object, + uint32_t insert_after_id, uint32_t offset); + + // - |message_.fresh_id| must not be used by the module. + // - |message_.object| must be a result id that is a legitimate operand for + // OpCopyObject. In particular, it must be the id of an instruction that + // has a result type + // - |message_.object| must not be the target of any decoration. + // TODO(afd): consider copying decorations along with objects. + // - |message_.insert_after_id| must be the result id of an instruction + // 'base' in some block 'blk'. + // - 'blk' must contain an instruction 'inst' located |message_.offset| + // instructions after 'base' (if |message_.offset| = 0 then 'inst' = + // 'base'). + // - It must be legal to insert an OpCopyObject instruction directly + // before 'inst'. + // - |message_object| must be available directly before 'inst'. + bool IsApplicable(opt::IRContext* context, + const FactManager& fact_manager) const override; + + // - A new instruction, + // %|message_.fresh_id| = OpCopyObject %ty %|message_.object| + // is added directly before the instruction at |message_.insert_after_id| + + // |message_|.offset, where %ty is the type of |message_.object|. + // - The fact that |message_.fresh_id| and |message_.object| are synonyms + // is added to the fact manager. + void Apply(opt::IRContext* context, FactManager* fact_manager) const override; + + protobufs::Transformation ToMessage() const override; + + private: + protobufs::TransformationCopyObject message_; +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_TRANSFORMATION_COPY_OBJECT_H_ diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp index a8c33ded9..a2da371c6 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp +++ b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.cpp @@ -26,147 +26,104 @@ TransformationSplitBlock::TransformationSplitBlock( const spvtools::fuzz::protobufs::TransformationSplitBlock& message) : message_(message) {} -TransformationSplitBlock::TransformationSplitBlock(uint32_t result_id, +TransformationSplitBlock::TransformationSplitBlock(uint32_t base_instruction_id, uint32_t offset, uint32_t fresh_id) { - message_.set_result_id(result_id); + message_.set_base_instruction_id(base_instruction_id); message_.set_offset(offset); message_.set_fresh_id(fresh_id); } -std::pair -TransformationSplitBlock::FindInstToSplitBefore(opt::BasicBlock* block) const { - // There are three possibilities: - // (1) the transformation wants to split at some offset from the block's - // label. - // (2) the transformation wants to split at some offset from a - // non-label instruction inside the block. - // (3) the split assocaiated with this transformation has nothing to do with - // this block - if (message_.result_id() == block->id()) { - // Case (1). - if (message_.offset() == 0) { - // The offset is not allowed to be 0: this would mean splitting before the - // block's label. - // By returning (true, block->end()), we indicate that we did find the - // instruction (so that it is not worth searching further for it), but - // that splitting will not be possible. - return {true, block->end()}; - } - // Conceptually, the first instruction in the block is [label + 1]. - // We thus start from 1 when applying the offset. - auto inst_it = block->begin(); - for (uint32_t i = 1; i < message_.offset() && inst_it != block->end(); - i++) { - ++inst_it; - } - // This is either the desired instruction, or the end of the block. - return {true, inst_it}; - } - for (auto inst_it = block->begin(); inst_it != block->end(); ++inst_it) { - if (message_.result_id() == inst_it->result_id()) { - // Case (2): we have found the base instruction; we now apply the offset. - for (uint32_t i = 0; i < message_.offset() && inst_it != block->end(); - i++) { - ++inst_it; - } - // This is either the desired instruction, or the end of the block. - return {true, inst_it}; - } - } - // Case (3). - return {false, block->end()}; -} - bool TransformationSplitBlock::IsApplicable( opt::IRContext* context, const FactManager& /*unused*/) const { if (!fuzzerutil::IsFreshId(context, message_.fresh_id())) { // We require the id for the new block to be unused. return false; } - // Consider every block in every function. - for (auto& function : *context->module()) { - for (auto& block : function) { - auto maybe_split_before = FindInstToSplitBefore(&block); - if (!maybe_split_before.first) { - continue; - } - if (maybe_split_before.second == block.end()) { - // The base instruction was found, but the offset was inappropriate. - return false; - } - if (block.IsLoopHeader()) { - // We cannot split a loop header block: back-edges would become invalid. - return false; - } - auto split_before = maybe_split_before.second; - if (split_before->PreviousNode() && - split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) { - // We cannot split directly after a selection merge: this would separate - // the merge from its associated branch or switch operation. - return false; - } - if (split_before->opcode() == SpvOpVariable) { - // We cannot split directly after a variable; variables in a function - // must be contiguous in the entry block. - return false; - } - if (split_before->opcode() == SpvOpPhi && - split_before->NumInOperands() != 2) { - // We cannot split before an OpPhi unless the OpPhi has exactly one - // associated incoming edge. - return false; - } - return true; - } + auto base_instruction = + context->get_def_use_mgr()->GetDef(message_.base_instruction_id()); + if (!base_instruction) { + // The instruction describing the block we should split does not exist. + return false; } - return false; + auto block_containing_base_instruction = + context->get_instr_block(base_instruction); + if (!block_containing_base_instruction) { + // The instruction describing the block we should split is not contained in + // a block. + return false; + } + + if (block_containing_base_instruction->IsLoopHeader()) { + // We cannot split a loop header block: back-edges would become invalid. + return false; + } + + auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset( + block_containing_base_instruction, base_instruction, message_.offset()); + if (split_before == block_containing_base_instruction->end()) { + // The offset was inappropriate. + return false; + } + if (split_before->PreviousNode() && + split_before->PreviousNode()->opcode() == SpvOpSelectionMerge) { + // We cannot split directly after a selection merge: this would separate + // the merge from its associated branch or switch operation. + return false; + } + if (split_before->opcode() == SpvOpVariable) { + // We cannot split directly after a variable; variables in a function + // must be contiguous in the entry block. + return false; + } + // We cannot split before an OpPhi unless the OpPhi has exactly one + // associated incoming edge. + return !(split_before->opcode() == SpvOpPhi && + split_before->NumInOperands() != 2); } void TransformationSplitBlock::Apply(opt::IRContext* context, FactManager* /*unused*/) const { - for (auto& function : *context->module()) { - for (auto& block : function) { - auto maybe_split_before = FindInstToSplitBefore(&block); - if (!maybe_split_before.first) { - continue; - } - assert(maybe_split_before.second != block.end() && - "If the transformation is applicable, we should have an " - "instruction to split on."); - // We need to make sure the module's id bound is large enough to add the - // fresh id. - fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); - // Split the block. - auto new_bb = block.SplitBasicBlock(context, message_.fresh_id(), - maybe_split_before.second); - // The split does not automatically add a branch between the two parts of - // the original block, so we add one. - block.AddInstruction(MakeUnique( + auto base_instruction = + context->get_def_use_mgr()->GetDef(message_.base_instruction_id()); + assert(base_instruction && "Base instruction must exist"); + auto block_containing_base_instruction = + context->get_instr_block(base_instruction); + assert(block_containing_base_instruction && + "Base instruction must be in a block"); + auto split_before = fuzzerutil::GetIteratorForBaseInstructionAndOffset( + block_containing_base_instruction, base_instruction, message_.offset()); + assert(split_before != block_containing_base_instruction->end() && + "If the transformation is applicable, we should have an " + "instruction to split on."); + // We need to make sure the module's id bound is large enough to add the + // fresh id. + fuzzerutil::UpdateModuleIdBound(context, message_.fresh_id()); + // Split the block. + auto new_bb = block_containing_base_instruction->SplitBasicBlock( + context, message_.fresh_id(), split_before); + // The split does not automatically add a branch between the two parts of + // the original block, so we add one. + block_containing_base_instruction->AddInstruction( + MakeUnique( context, SpvOpBranch, 0, 0, std::initializer_list{ opt::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {message_.fresh_id()})})); - // If we split before OpPhi instructions, we need to update their - // predecessor operand so that the block they used to be inside is now the - // predecessor. - new_bb->ForEachPhiInst([&block](opt::Instruction* phi_inst) { + // If we split before OpPhi instructions, we need to update their + // predecessor operand so that the block they used to be inside is now the + // predecessor. + new_bb->ForEachPhiInst( + [block_containing_base_instruction](opt::Instruction* phi_inst) { // The following assertion is a sanity check. It is guaranteed to hold // if IsApplicable holds. assert(phi_inst->NumInOperands() == 2 && "We can only split a block before an OpPhi if block has exactly " "one predecessor."); - phi_inst->SetInOperand(1, {block.id()}); + phi_inst->SetInOperand(1, {block_containing_base_instruction->id()}); }); - // Invalidate all analyses - context->InvalidateAnalysesExceptFor( - opt::IRContext::Analysis::kAnalysisNone); - return; - } - } - assert(0 && - "Should be unreachable: it should have been possible to apply this " - "transformation."); + // Invalidate all analyses + context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone); } protobufs::Transformation TransformationSplitBlock::ToMessage() const { diff --git a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h index ef4aa7596..4a7095ac0 100644 --- a/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h +++ b/3rdparty/spirv-tools/source/fuzz/transformation_split_block.h @@ -28,13 +28,13 @@ class TransformationSplitBlock : public Transformation { explicit TransformationSplitBlock( const protobufs::TransformationSplitBlock& message); - TransformationSplitBlock(uint32_t result_id, uint32_t offset, + TransformationSplitBlock(uint32_t base_instruction_id, uint32_t offset, uint32_t fresh_id); - // - |message_.result_id| must be the result id of an instruction 'base' in - // some block 'blk'. + // - |message_.base_instruction_id| must be the result id of an instruction + // 'base' in some block 'blk'. // - 'blk' must contain an instruction 'inst' located |message_.offset| - // instructions after 'inst' (if |message_.offset| = 0 then 'inst' = + // instructions after 'base' (if |message_.offset| = 0 then 'inst' = // 'base'). // - Splitting 'blk' at 'inst', so that all instructions from 'inst' onwards // appear in a new block that 'blk' directly jumps to must be valid. @@ -52,14 +52,6 @@ class TransformationSplitBlock : public Transformation { protobufs::Transformation ToMessage() const override; private: - // Returns: - // - (true, block->end()) if the relevant instruction is in this block - // but inapplicable - // - (true, it) if 'it' is an iterator for the relevant instruction - // - (false, _) otherwise. - std::pair FindInstToSplitBefore( - opt::BasicBlock* block) const; - protobufs::TransformationSplitBlock message_; }; diff --git a/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.cpp b/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.cpp index 8c758e421..90fd85e96 100644 --- a/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.cpp +++ b/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.cpp @@ -14,7 +14,7 @@ #include "source/fuzz/uniform_buffer_element_descriptor.h" -#include +#include namespace spvtools { namespace fuzz { diff --git a/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.h b/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.h index 23a16f0df..d35de5762 100644 --- a/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.h +++ b/3rdparty/spirv-tools/source/fuzz/uniform_buffer_element_descriptor.h @@ -15,7 +15,6 @@ #ifndef SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_ #define SOURCE_FUZZ_UNIFORM_BUFFER_ELEMENT_DESCRIPTOR_H_ -#include #include #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" @@ -25,8 +24,8 @@ namespace spvtools { namespace fuzz { -// Factory method to create a uniform buffer element descriptor message from an -// id and list of indices. +// Factory method to create a uniform buffer element descriptor message from +// descriptor set and binding ids and a list of indices. protobufs::UniformBufferElementDescriptor MakeUniformBufferElementDescriptor( uint32_t descriptor_set, uint32_t binding, std::vector&& indices); diff --git a/3rdparty/spirv-tools/source/opt/CMakeLists.txt b/3rdparty/spirv-tools/source/opt/CMakeLists.txt index 3e1280e62..2ebad512a 100644 --- a/3rdparty/spirv-tools/source/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/opt/CMakeLists.txt @@ -33,6 +33,7 @@ set(SPIRV_TOOLS_OPT_SOURCES decompose_initialized_variables_pass.h decoration_manager.h def_use_manager.h + desc_sroa.h dominator_analysis.h dominator_tree.h eliminate_dead_constant_pass.h @@ -48,6 +49,7 @@ set(SPIRV_TOOLS_OPT_SOURCES freeze_spec_constant_value_pass.h function.h generate_webgpu_initializers_pass.h + graphics_robust_access_pass.h if_conversion.h inline_exhaustive_pass.h inline_opaque_pass.h @@ -133,6 +135,7 @@ set(SPIRV_TOOLS_OPT_SOURCES decompose_initialized_variables_pass.cpp decoration_manager.cpp def_use_manager.cpp + desc_sroa.cpp dominator_analysis.cpp dominator_tree.cpp eliminate_dead_constant_pass.cpp @@ -147,6 +150,7 @@ set(SPIRV_TOOLS_OPT_SOURCES fold_spec_constant_op_and_composite_pass.cpp freeze_spec_constant_value_pass.cpp function.cpp + graphics_robust_access_pass.cpp generate_webgpu_initializers_pass.cpp if_conversion.cpp inline_exhaustive_pass.cpp diff --git a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp index 11a9574d9..04bfea1df 100644 --- a/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/aggressive_dead_code_elim_pass.cpp @@ -836,6 +836,15 @@ bool AggressiveDCEPass::ProcessGlobalValues() { // attributes here. for (auto& val : get_module()->types_values()) { if (IsDead(&val)) { + // Save forwarded pointer if pointer is live since closure does not mark + // this live as it does not have a result id. This is a little too + // conservative since it is not known if the structure type that needed + // it is still live. TODO(greg-lunarg): Only save if needed. + if (val.opcode() == SpvOpTypeForwardPointer) { + uint32_t ptr_ty_id = val.GetSingleWordInOperand(0); + Instruction* ptr_ty_inst = get_def_use_mgr()->GetDef(ptr_ty_id); + if (!IsDead(ptr_ty_inst)) continue; + } to_kill_.push_back(&val); } } @@ -918,6 +927,7 @@ void AggressiveDCEPass::InitExtensions() { "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_EXT_fragment_invocation_density", + "SPV_EXT_physical_storage_buffer", }); } diff --git a/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp index 10fcde408..23a799821 100644 --- a/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp +++ b/3rdparty/spirv-tools/source/opt/const_folding_rules.cpp @@ -55,6 +55,9 @@ ConstantFoldingRule FoldExtractWithConstants() { auto cc = c->AsCompositeConstant(); assert(cc != nullptr); auto components = cc->GetComponents(); + // Protect against invalid IR. Refuse to fold if the index is out + // of bounds. + if (element_index >= components.size()) return nullptr; c = components[element_index]; } return c; diff --git a/3rdparty/spirv-tools/source/opt/constants.cpp b/3rdparty/spirv-tools/source/opt/constants.cpp index 3c05f9e9e..5c1468be5 100644 --- a/3rdparty/spirv-tools/source/opt/constants.cpp +++ b/3rdparty/spirv-tools/source/opt/constants.cpp @@ -103,6 +103,45 @@ int64_t Constant::GetS64() const { } } +uint64_t Constant::GetZeroExtendedValue() const { + const auto* int_type = type()->AsInteger(); + assert(int_type != nullptr); + const auto width = int_type->width(); + assert(width <= 64); + + uint64_t value = 0; + if (const IntConstant* ic = AsIntConstant()) { + if (width <= 32) { + value = ic->GetU32BitValue(); + } else { + value = ic->GetU64BitValue(); + } + } else { + assert(AsNullConstant() && "Must be an integer constant."); + } + return value; +} + +int64_t Constant::GetSignExtendedValue() const { + const auto* int_type = type()->AsInteger(); + assert(int_type != nullptr); + const auto width = int_type->width(); + assert(width <= 64); + + int64_t value = 0; + if (const IntConstant* ic = AsIntConstant()) { + if (width <= 32) { + // Let the C++ compiler do the sign extension. + value = int64_t(ic->GetS32BitValue()); + } else { + value = ic->GetS64BitValue(); + } + } else { + assert(AsNullConstant() && "Must be an integer constant."); + } + return value; +} + ConstantManager::ConstantManager(IRContext* ctx) : ctx_(ctx) { // Populate the constant table with values from constant declarations in the // module. The values of each OpConstant declaration is the identity @@ -252,7 +291,7 @@ std::unique_ptr ConstantManager::CreateConstant( } } -const Constant* ConstantManager::GetConstantFromInst(Instruction* inst) { +const Constant* ConstantManager::GetConstantFromInst(const Instruction* inst) { std::vector literal_words_or_ids; // Collect the constant defining literals or component ids. diff --git a/3rdparty/spirv-tools/source/opt/constants.h b/3rdparty/spirv-tools/source/opt/constants.h index 8ac4ce630..34855001c 100644 --- a/3rdparty/spirv-tools/source/opt/constants.h +++ b/3rdparty/spirv-tools/source/opt/constants.h @@ -116,6 +116,14 @@ class Constant { // Integer type. int64_t GetS64() const; + // Returns the zero-extended representation of an integer constant. Must + // be an integral constant of at most 64 bits. + uint64_t GetZeroExtendedValue() const; + + // Returns the sign-extended representation of an integer constant. Must + // be an integral constant of at most 64 bits. + int64_t GetSignExtendedValue() const; + // Returns true if the constant is a zero or a composite containing 0s. virtual bool IsZero() const { return false; } @@ -514,7 +522,7 @@ class ConstantManager { // Gets or creates a Constant instance to hold the constant value of the given // instruction. It returns a pointer to a Constant instance or nullptr if it // could not create the constant. - const Constant* GetConstantFromInst(Instruction* inst); + const Constant* GetConstantFromInst(const Instruction* inst); // Gets or creates a constant defining instruction for the given Constant |c|. // If |c| had already been defined, it returns a pointer to the existing diff --git a/3rdparty/spirv-tools/source/opt/desc_sroa.cpp b/3rdparty/spirv-tools/source/opt/desc_sroa.cpp new file mode 100644 index 000000000..36256ffaf --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/desc_sroa.cpp @@ -0,0 +1,255 @@ +// 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/desc_sroa.h" + +#include + +namespace spvtools { +namespace opt { + +Pass::Status DescriptorScalarReplacement::Process() { + bool modified = false; + + std::vector vars_to_kill; + + for (Instruction& var : context()->types_values()) { + if (IsCandidate(&var)) { + modified = true; + if (!ReplaceCandidate(&var)) { + return Status::Failure; + } + vars_to_kill.push_back(&var); + } + } + + for (Instruction* var : vars_to_kill) { + context()->KillInst(var); + } + + return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange); +} + +bool DescriptorScalarReplacement::IsCandidate(Instruction* var) { + if (var->opcode() != SpvOpVariable) { + return false; + } + + uint32_t ptr_type_id = var->type_id(); + Instruction* ptr_type_inst = + context()->get_def_use_mgr()->GetDef(ptr_type_id); + if (ptr_type_inst->opcode() != SpvOpTypePointer) { + return false; + } + + uint32_t var_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* var_type_inst = + context()->get_def_use_mgr()->GetDef(var_type_id); + if (var_type_inst->opcode() != SpvOpTypeArray) { + return false; + } + + bool has_desc_set_decoration = false; + context()->get_decoration_mgr()->ForEachDecoration( + var->result_id(), SpvDecorationDescriptorSet, + [&has_desc_set_decoration](const Instruction&) { + has_desc_set_decoration = true; + }); + if (!has_desc_set_decoration) { + return false; + } + + bool has_binding_decoration = false; + context()->get_decoration_mgr()->ForEachDecoration( + var->result_id(), SpvDecorationBinding, + [&has_binding_decoration](const Instruction&) { + has_binding_decoration = true; + }); + if (!has_binding_decoration) { + return false; + } + + return true; +} + +bool DescriptorScalarReplacement::ReplaceCandidate(Instruction* var) { + std::vector work_list; + bool failed = !get_def_use_mgr()->WhileEachUser( + var->result_id(), [this, &work_list](Instruction* use) { + if (use->opcode() == SpvOpName) { + return true; + } + + if (use->IsDecoration()) { + return true; + } + + switch (use->opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + work_list.push_back(use); + return true; + default: + context()->EmitErrorMessage( + "Variable cannot be replaced: invalid instruction", use); + return false; + } + return true; + }); + + if (failed) { + return false; + } + + for (Instruction* use : work_list) { + if (!ReplaceAccessChain(var, use)) { + return false; + } + } + return true; +} + +bool DescriptorScalarReplacement::ReplaceAccessChain(Instruction* var, + Instruction* use) { + if (use->NumInOperands() <= 1) { + context()->EmitErrorMessage( + "Variable cannot be replaced: invalid instruction", use); + return false; + } + + uint32_t idx_id = use->GetSingleWordInOperand(1); + const analysis::Constant* idx_const = + context()->get_constant_mgr()->FindDeclaredConstant(idx_id); + if (idx_const == nullptr) { + context()->EmitErrorMessage("Variable cannot be replaced: invalid index", + use); + return false; + } + + uint32_t idx = idx_const->GetU32(); + uint32_t replacement_var = GetReplacementVariable(var, idx); + + if (use->NumInOperands() == 2) { + // We are not indexing into the replacement variable. We can replaces the + // access chain with the replacement varibale itself. + context()->ReplaceAllUsesWith(use->result_id(), replacement_var); + context()->KillInst(use); + return true; + } + + // We need to build a new access chain with the replacement variable as the + // base address. + Instruction::OperandList new_operands; + + // Same result id and result type. + new_operands.emplace_back(use->GetOperand(0)); + new_operands.emplace_back(use->GetOperand(1)); + + // Use the replacement variable as the base address. + new_operands.push_back({SPV_OPERAND_TYPE_ID, {replacement_var}}); + + // Drop the first index because it is consumed by the replacment, and copy the + // rest. + for (uint32_t i = 4; i < use->NumOperands(); i++) { + new_operands.emplace_back(use->GetOperand(i)); + } + + use->ReplaceOperands(new_operands); + context()->UpdateDefUse(use); + return true; +} + +uint32_t DescriptorScalarReplacement::GetReplacementVariable(Instruction* var, + uint32_t idx) { + auto replacement_vars = replacement_variables_.find(var); + if (replacement_vars == replacement_variables_.end()) { + uint32_t ptr_type_id = var->type_id(); + Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); + assert(ptr_type_inst->opcode() == SpvOpTypePointer && + "Variable should be a pointer to an array."); + uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id); + assert(arr_type_inst->opcode() == SpvOpTypeArray && + "Variable should be a pointer to an array."); + + uint32_t array_len_id = arr_type_inst->GetSingleWordInOperand(1); + const analysis::Constant* array_len_const = + context()->get_constant_mgr()->FindDeclaredConstant(array_len_id); + assert(array_len_const != nullptr && "Array length must be a constant."); + uint32_t array_len = array_len_const->GetU32(); + + replacement_vars = replacement_variables_ + .insert({var, std::vector(array_len, 0)}) + .first; + } + + if (replacement_vars->second[idx] == 0) { + replacement_vars->second[idx] = CreateReplacementVariable(var, idx); + } + + return replacement_vars->second[idx]; +} + +uint32_t DescriptorScalarReplacement::CreateReplacementVariable( + Instruction* var, uint32_t idx) { + // The storage class for the new variable is the same as the original. + SpvStorageClass storage_class = + static_cast(var->GetSingleWordInOperand(0)); + + // The type for the new variable will be a pointer to type of the elements of + // the array. + uint32_t ptr_type_id = var->type_id(); + Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id); + assert(ptr_type_inst->opcode() == SpvOpTypePointer && + "Variable should be a pointer to an array."); + uint32_t arr_type_id = ptr_type_inst->GetSingleWordInOperand(1); + Instruction* arr_type_inst = get_def_use_mgr()->GetDef(arr_type_id); + assert(arr_type_inst->opcode() == SpvOpTypeArray && + "Variable should be a pointer to an array."); + uint32_t element_type_id = arr_type_inst->GetSingleWordInOperand(0); + + uint32_t ptr_element_type_id = context()->get_type_mgr()->FindPointerToType( + element_type_id, storage_class); + + // Create the variable. + uint32_t id = TakeNextId(); + std::unique_ptr variable( + new Instruction(context(), SpvOpVariable, ptr_element_type_id, id, + std::initializer_list{ + {SPV_OPERAND_TYPE_STORAGE_CLASS, + {static_cast(storage_class)}}})); + context()->AddGlobalValue(std::move(variable)); + + // Copy all of the decorations to the new variable. The only difference is + // the Binding decoration needs to be adjusted. + for (auto old_decoration : + get_decoration_mgr()->GetDecorationsFor(var->result_id(), true)) { + assert(old_decoration->opcode() == SpvOpDecorate); + std::unique_ptr new_decoration( + old_decoration->Clone(context())); + new_decoration->SetInOperand(0, {id}); + + uint32_t decoration = new_decoration->GetSingleWordInOperand(1u); + if (decoration == SpvDecorationBinding) { + uint32_t new_binding = new_decoration->GetSingleWordInOperand(2) + idx; + new_decoration->SetInOperand(2, {new_binding}); + } + context()->AddAnnotationInst(std::move(new_decoration)); + } + + return id; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/desc_sroa.h b/3rdparty/spirv-tools/source/opt/desc_sroa.h new file mode 100644 index 000000000..a95c6b582 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/desc_sroa.h @@ -0,0 +1,84 @@ +// 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_DESC_SROA_H_ +#define SOURCE_OPT_DESC_SROA_H_ + +#include +#include +#include +#include +#include +#include + +#include "source/opt/function.h" +#include "source/opt/pass.h" +#include "source/opt/type_manager.h" + +namespace spvtools { +namespace opt { + +// Documented in optimizer.hpp +class DescriptorScalarReplacement : public Pass { + public: + DescriptorScalarReplacement() {} + + const char* name() const override { return "descriptor-scalar-replacement"; } + + Status Process() override; + + IRContext::Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisCombinators | IRContext::kAnalysisCFG | + IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; + } + + private: + // Returns true if |var| is an OpVariable instruction that represents a + // descriptor array. These are the variables that we want to replace. + bool IsCandidate(Instruction* var); + + // Replaces all references to |var| by new variables, one for each element of + // the array |var|. The binding for the new variables corresponding to + // element i will be the binding of |var| plus i. Returns true if successful. + bool ReplaceCandidate(Instruction* var); + + // Replaces the base address |var| in the OpAccessChain or + // OpInBoundsAccessChain instruction |use| by the variable that the access + // chain accesses. The first index in |use| must be an |OpConstant|. Returns + // |true| if successful. + bool ReplaceAccessChain(Instruction* var, Instruction* use); + + // Returns the id of the variable that will be used to replace the |idx|th + // element of |var|. The variable is created if it has not already been + // created. + uint32_t GetReplacementVariable(Instruction* var, uint32_t idx); + + // Returns the id of a new variable that can be used to replace the |idx|th + // element of |var|. + uint32_t CreateReplacementVariable(Instruction* var, uint32_t idx); + + // A map from an OpVariable instruction to the set of variables that will be + // used to replace it. The entry |replacement_variables_[var][i]| is the id of + // a variable that will be used in the place of the the ith element of the + // array |var|. If the entry is |0|, then the variable has not been + // created yet. + std::map> replacement_variables_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_DESC_SROA_H_ diff --git a/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp new file mode 100644 index 000000000..dd60e8c62 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.cpp @@ -0,0 +1,968 @@ +// 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. + +// This pass injects code in a graphics shader to implement guarantees +// satisfying Vulkan's robustBufferAcces rules. Robust access rules permit +// an out-of-bounds access to be redirected to an access of the same type +// (load, store, etc.) but within the same root object. +// +// We assume baseline functionality in Vulkan, i.e. the module uses +// logical addressing mode, without VK_KHR_variable_pointers. +// +// - Logical addressing mode implies: +// - Each root pointer (a pointer that exists other than by the +// execution of a shader instruction) is the result of an OpVariable. +// +// - Instructions that result in pointers are: +// OpVariable +// OpAccessChain +// OpInBoundsAccessChain +// OpFunctionParameter +// OpImageTexelPointer +// OpCopyObject +// +// - Instructions that use a pointer are: +// OpLoad +// OpStore +// OpAccessChain +// OpInBoundsAccessChain +// OpFunctionCall +// OpImageTexelPointer +// OpCopyMemory +// OpCopyObject +// all OpAtomic* instructions +// +// We classify pointer-users into: +// - Accesses: +// - OpLoad +// - OpStore +// - OpAtomic* +// - OpCopyMemory +// +// - Address calculations: +// - OpAccessChain +// - OpInBoundsAccessChain +// +// - Pass-through: +// - OpFunctionCall +// - OpFunctionParameter +// - OpCopyObject +// +// The strategy is: +// +// - Handle only logical addressing mode. In particular, don't handle a module +// if it uses one of the variable-pointers capabilities. +// +// - Don't handle modules using capability RuntimeDescriptorArrayEXT. So the +// only runtime arrays are those that are the last member in a +// Block-decorated struct. This allows us to feasibly/easily compute the +// length of the runtime array. See below. +// +// - The memory locations accessed by OpLoad, OpStore, OpCopyMemory, and +// OpAtomic* are determined by their pointer parameter or parameters. +// Pointers are always (correctly) typed and so the address and number of +// consecutive locations are fully determined by the pointer. +// +// - A pointer value orginates as one of few cases: +// +// - OpVariable for an interface object or an array of them: image, +// buffer (UBO or SSBO), sampler, sampled-image, push-constant, input +// variable, output variable. The execution environment is responsible for +// allocating the correct amount of storage for these, and for ensuring +// each resource bound to such a variable is big enough to contain the +// SPIR-V pointee type of the variable. +// +// - OpVariable for a non-interface object. These are variables in +// Workgroup, Private, and Function storage classes. The compiler ensures +// the underlying allocation is big enough to store the entire SPIR-V +// pointee type of the variable. +// +// - An OpFunctionParameter. This always maps to a pointer parameter to an +// OpFunctionCall. +// +// - In logical addressing mode, these are severely limited: +// "Any pointer operand to an OpFunctionCall must be: +// - a memory object declaration, or +// - a pointer to an element in an array that is a memory object +// declaration, where the element type is OpTypeSampler or OpTypeImage" +// +// - This has an important simplifying consequence: +// +// - When looking for a pointer to the structure containing a runtime +// array, you begin with a pointer to the runtime array and trace +// backward in the function. You never have to trace back beyond +// your function call boundary. So you can't take a partial access +// chain into an SSBO, then pass that pointer into a function. So +// we don't resort to using fat pointers to compute array length. +// We can trace back to a pointer to the containing structure, +// and use that in an OpArrayLength instruction. (The structure type +// gives us the member index of the runtime array.) +// +// - Otherwise, the pointer type fully encodes the range of valid +// addresses. In particular, the type of a pointer to an aggregate +// value fully encodes the range of indices when indexing into +// that aggregate. +// +// - The pointer is the result of an access chain instruction. We clamp +// indices contributing to address calculations. As noted above, the +// valid ranges are either bound by the length of a runtime array, or +// by the type of the base pointer. The length of a runtime array is +// the result of an OpArrayLength instruction acting on the pointer of +// the containing structure as noted above. +// +// - TODO(dneto): OpImageTexelPointer: +// - Clamp coordinate to the image size returned by OpImageQuerySize +// - If multi-sampled, clamp the sample index to the count returned by +// OpImageQuerySamples. +// - If not multi-sampled, set the sample index to 0. +// +// - Rely on the external validator to check that pointers are only +// used by the instructions as above. +// +// - Handles OpTypeRuntimeArray +// Track pointer back to original resource (pointer to struct), so we can +// query the runtime array size. +// + +#include "graphics_robust_access_pass.h" + +#include +#include +#include +#include +#include + +#include "constants.h" +#include "def_use_manager.h" +#include "function.h" +#include "ir_context.h" +#include "module.h" +#include "pass.h" +#include "source/diagnostic.h" +#include "source/util/make_unique.h" +#include "spirv-tools/libspirv.h" +#include "spirv/unified1/GLSL.std.450.h" +#include "spirv/unified1/spirv.h" +#include "type_manager.h" +#include "types.h" + +namespace spvtools { +namespace opt { + +using opt::BasicBlock; +using opt::Instruction; +using opt::Operand; +using spvtools::MakeUnique; + +GraphicsRobustAccessPass::GraphicsRobustAccessPass() : module_status_() {} + +Pass::Status GraphicsRobustAccessPass::Process() { + module_status_ = PerModuleState(); + + ProcessCurrentModule(); + + auto result = module_status_.failed + ? Status::Failure + : (module_status_.modified ? Status::SuccessWithChange + : Status::SuccessWithoutChange); + + return result; +} + +spvtools::DiagnosticStream GraphicsRobustAccessPass::Fail() { + module_status_.failed = true; + // We don't really have a position, and we'll ignore the result. + return std::move( + spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY) + << name() << ": "); +} + +spv_result_t GraphicsRobustAccessPass::IsCompatibleModule() { + auto* feature_mgr = context()->get_feature_mgr(); + if (!feature_mgr->HasCapability(SpvCapabilityShader)) + return Fail() << "Can only process Shader modules"; + if (feature_mgr->HasCapability(SpvCapabilityVariablePointers)) + return Fail() << "Can't process modules with VariablePointers capability"; + if (feature_mgr->HasCapability(SpvCapabilityVariablePointersStorageBuffer)) + return Fail() << "Can't process modules with VariablePointersStorageBuffer " + "capability"; + if (feature_mgr->HasCapability(SpvCapabilityRuntimeDescriptorArrayEXT)) { + // These have a RuntimeArray outside of Block-decorated struct. There + // is no way to compute the array length from within SPIR-V. + return Fail() << "Can't process modules with RuntimeDescriptorArrayEXT " + "capability"; + } + + { + auto* inst = context()->module()->GetMemoryModel(); + const auto addressing_model = inst->GetSingleWordOperand(0); + if (addressing_model != SpvAddressingModelLogical) + return Fail() << "Addressing model must be Logical. Found " + << inst->PrettyPrint(); + } + return SPV_SUCCESS; +} + +spv_result_t GraphicsRobustAccessPass::ProcessCurrentModule() { + auto err = IsCompatibleModule(); + if (err != SPV_SUCCESS) return err; + + ProcessFunction fn = [this](opt::Function* f) { return ProcessAFunction(f); }; + module_status_.modified |= context()->ProcessReachableCallTree(fn); + + // Need something here. It's the price we pay for easier failure paths. + return SPV_SUCCESS; +} + +bool GraphicsRobustAccessPass::ProcessAFunction(opt::Function* function) { + // Ensure that all pointers computed inside a function are within bounds. + // Find the access chains in this block before trying to modify them. + std::vector access_chains; + std::vector image_texel_pointers; + for (auto& block : *function) { + for (auto& inst : block) { + switch (inst.opcode()) { + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: + access_chains.push_back(&inst); + break; + case SpvOpImageTexelPointer: + image_texel_pointers.push_back(&inst); + break; + default: + break; + } + } + } + for (auto* inst : access_chains) { + ClampIndicesForAccessChain(inst); + } + + for (auto* inst : image_texel_pointers) { + if (SPV_SUCCESS != ClampCoordinateForImageTexelPointer(inst)) break; + } + return module_status_.modified; +} + +void GraphicsRobustAccessPass::ClampIndicesForAccessChain( + Instruction* access_chain) { + Instruction& inst = *access_chain; + + auto* constant_mgr = context()->get_constant_mgr(); + auto* def_use_mgr = context()->get_def_use_mgr(); + auto* type_mgr = context()->get_type_mgr(); + + // Replaces one of the OpAccessChain index operands with a new value. + // Updates def-use analysis. + auto replace_index = [&inst, def_use_mgr](uint32_t operand_index, + Instruction* new_value) { + inst.SetOperand(operand_index, {new_value->result_id()}); + def_use_mgr->AnalyzeInstUse(&inst); + }; + + // Replaces one of the OpAccesssChain index operands with a clamped value. + // Replace the operand at |operand_index| with the value computed from + // unsigned_clamp(%old_value, %min_value, %max_value). It also analyzes + // the new instruction and records that them module is modified. + auto clamp_index = [&inst, this, &replace_index]( + uint32_t operand_index, Instruction* old_value, + Instruction* min_value, Instruction* max_value) { + auto* clamp_inst = MakeClampInst(old_value, min_value, max_value, &inst); + replace_index(operand_index, clamp_inst); + }; + + // Ensures the specified index of access chain |inst| has a value that is + // at most |count| - 1. If the index is already a constant value less than + // |count| then no change is made. + auto clamp_to_literal_count = [&inst, this, &constant_mgr, &type_mgr, + &replace_index, &clamp_index]( + uint32_t operand_index, uint64_t count) { + Instruction* index_inst = + this->GetDef(inst.GetSingleWordOperand(operand_index)); + const auto* index_type = + type_mgr->GetType(index_inst->type_id())->AsInteger(); + assert(index_type); + if (count <= 1) { + // Replace the index with 0. + replace_index(operand_index, GetValueForType(0, index_type)); + return; + } + + const auto index_width = index_type->width(); + + // If the index is a constant then |index_constant| will not be a null + // pointer. (If index is an |OpConstantNull| then it |index_constant| will + // not be a null pointer.) Since access chain indices must be scalar + // integers, this can't be a spec constant. + if (auto* index_constant = constant_mgr->GetConstantFromInst(index_inst)) { + auto* int_index_constant = index_constant->AsIntConstant(); + int64_t value = 0; + // OpAccessChain indices are treated as signed. So get the signed + // constant value here. + if (index_width <= 32) { + value = int64_t(int_index_constant->GetS32BitValue()); + } else if (index_width <= 64) { + value = int_index_constant->GetS64BitValue(); + } else { + this->Fail() << "Can't handle indices wider than 64 bits, found " + "constant index with " + << index_type->width() << "bits"; + return; + } + if (value < 0) { + replace_index(operand_index, GetValueForType(0, index_type)); + } else if (uint64_t(value) < count) { + // Nothing to do. + return; + } else { + // Replace with count - 1. + assert(count > 0); // Already took care of this case above. + replace_index(operand_index, GetValueForType(count - 1, index_type)); + } + } else { + // Generate a clamp instruction. + + // Compute the bit width of a viable type to hold (count-1). + const auto maxval = count - 1; + const auto* maxval_type = index_type; + // Look for a bit width, up to 64 bits wide, to fit maxval. + uint32_t maxval_width = index_width; + while ((maxval_width < 64) && (0 != (maxval >> maxval_width))) { + maxval_width *= 2; + } + // Widen the index value if necessary + if (maxval_width > index_width) { + // Find the wider type. We only need this case if a constant (array) + // bound is too big. This never requires us to *add* a capability + // declaration for Int64 because the existence of the array bound would + // already have required that declaration. + index_inst = WidenInteger(index_type->IsSigned(), maxval_width, + index_inst, &inst); + maxval_type = type_mgr->GetType(index_inst->type_id())->AsInteger(); + } + // Finally, clamp the index. + clamp_index(operand_index, index_inst, GetValueForType(0, maxval_type), + GetValueForType(maxval, maxval_type)); + } + }; + + // Ensures the specified index of access chain |inst| has a value that is at + // most the value of |count_inst| minus 1, where |count_inst| is treated as an + // unsigned integer. + auto clamp_to_count = [&inst, this, &constant_mgr, &clamp_to_literal_count, + &clamp_index, &type_mgr](uint32_t operand_index, + Instruction* count_inst) { + Instruction* index_inst = + this->GetDef(inst.GetSingleWordOperand(operand_index)); + const auto* index_type = + type_mgr->GetType(index_inst->type_id())->AsInteger(); + const auto* count_type = + type_mgr->GetType(count_inst->type_id())->AsInteger(); + assert(index_type); + if (const auto* count_constant = + constant_mgr->GetConstantFromInst(count_inst)) { + uint64_t value = 0; + const auto width = count_constant->type()->AsInteger()->width(); + if (width <= 32) { + value = count_constant->AsIntConstant()->GetU32BitValue(); + } else if (width <= 64) { + value = count_constant->AsIntConstant()->GetU64BitValue(); + } else { + this->Fail() << "Can't handle indices wider than 64 bits, found " + "constant index with " + << index_type->width() << "bits"; + return; + } + clamp_to_literal_count(operand_index, value); + } else { + // Widen them to the same width. + const auto index_width = index_type->width(); + const auto count_width = count_type->width(); + const auto target_width = std::max(index_width, count_width); + // UConvert requires the result type to have 0 signedness. So enforce + // that here. + auto* wider_type = index_width < count_width ? count_type : index_type; + if (index_type->width() < target_width) { + // Access chain indices are treated as signed integers. + index_inst = WidenInteger(true, target_width, index_inst, &inst); + } else if (count_type->width() < target_width) { + // Assume type sizes are treated as unsigned. + count_inst = WidenInteger(false, target_width, count_inst, &inst); + } + // Compute count - 1. + // It doesn't matter if 1 is signed or unsigned. + auto* one = GetValueForType(1, wider_type); + auto* count_minus_1 = InsertInst( + &inst, SpvOpISub, type_mgr->GetId(wider_type), TakeNextId(), + {{SPV_OPERAND_TYPE_ID, {count_inst->result_id()}}, + {SPV_OPERAND_TYPE_ID, {one->result_id()}}}); + clamp_index(operand_index, index_inst, GetValueForType(0, wider_type), + count_minus_1); + } + }; + + const Instruction* base_inst = GetDef(inst.GetSingleWordInOperand(0)); + const Instruction* base_type = GetDef(base_inst->type_id()); + Instruction* pointee_type = GetDef(base_type->GetSingleWordInOperand(1)); + + // Walk the indices from earliest to latest, replacing indices with a + // clamped value, and updating the pointee_type. The order matters for + // the case when we have to compute the length of a runtime array. In + // that the algorithm relies on the fact that that the earlier indices + // have already been clamped. + const uint32_t num_operands = inst.NumOperands(); + for (uint32_t idx = 3; !module_status_.failed && idx < num_operands; ++idx) { + const uint32_t index_id = inst.GetSingleWordOperand(idx); + Instruction* index_inst = GetDef(index_id); + + switch (pointee_type->opcode()) { + case SpvOpTypeMatrix: // Use column count + case SpvOpTypeVector: // Use component count + { + const uint32_t count = pointee_type->GetSingleWordOperand(2); + clamp_to_literal_count(idx, count); + pointee_type = GetDef(pointee_type->GetSingleWordOperand(1)); + } break; + + case SpvOpTypeArray: { + // The array length can be a spec constant, so go through the general + // case. + Instruction* array_len = GetDef(pointee_type->GetSingleWordOperand(2)); + clamp_to_count(idx, array_len); + pointee_type = GetDef(pointee_type->GetSingleWordOperand(1)); + } break; + + case SpvOpTypeStruct: { + // SPIR-V requires the index to be an OpConstant. + // We need to know the index literal value so we can compute the next + // pointee type. + if (index_inst->opcode() != SpvOpConstant || + !constant_mgr->GetConstantFromInst(index_inst) + ->type() + ->AsInteger()) { + Fail() << "Member index into struct is not a constant integer: " + << index_inst->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << "\nin access chain: " + << inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + return; + } + const auto num_members = pointee_type->NumInOperands(); + const auto* index_constant = + constant_mgr->GetConstantFromInst(index_inst); + // Get the sign-extended value, since access index is always treated as + // signed. + const auto index_value = index_constant->GetSignExtendedValue(); + if (index_value < 0 || index_value >= num_members) { + Fail() << "Member index " << index_value + << " is out of bounds for struct type: " + << pointee_type->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES) + << "\nin access chain: " + << inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + return; + } + pointee_type = GetDef(pointee_type->GetSingleWordInOperand( + static_cast(index_value))); + // No need to clamp this index. We just checked that it's valid. + } break; + + case SpvOpTypeRuntimeArray: { + auto* array_len = MakeRuntimeArrayLengthInst(&inst, idx); + if (!array_len) { // We've already signaled an error. + return; + } + clamp_to_count(idx, array_len); + pointee_type = GetDef(pointee_type->GetSingleWordOperand(1)); + } break; + + default: + Fail() << " Unhandled pointee type for access chain " + << pointee_type->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + } + } +} + +uint32_t GraphicsRobustAccessPass::GetGlslInsts() { + if (module_status_.glsl_insts_id == 0) { + // This string serves double-duty as raw data for a string and for a vector + // of 32-bit words + const char glsl[] = "GLSL.std.450\0\0\0\0"; + const size_t glsl_str_byte_len = 16; + // Use an existing import if we can. + for (auto& inst : context()->module()->ext_inst_imports()) { + const auto& name_words = inst.GetInOperand(0).words; + if (0 == std::strncmp(reinterpret_cast(name_words.data()), + glsl, glsl_str_byte_len)) { + module_status_.glsl_insts_id = inst.result_id(); + } + } + if (module_status_.glsl_insts_id == 0) { + // Make a new import instruction. + module_status_.glsl_insts_id = TakeNextId(); + std::vector words(glsl_str_byte_len / sizeof(uint32_t)); + std::memcpy(words.data(), glsl, glsl_str_byte_len); + auto import_inst = MakeUnique( + context(), SpvOpExtInstImport, 0, module_status_.glsl_insts_id, + std::initializer_list{ + Operand{SPV_OPERAND_TYPE_LITERAL_STRING, std::move(words)}}); + Instruction* inst = import_inst.get(); + context()->module()->AddExtInstImport(std::move(import_inst)); + module_status_.modified = true; + context()->AnalyzeDefUse(inst); + // Reanalyze the feature list, since we added an extended instruction + // set improt. + context()->get_feature_mgr()->Analyze(context()->module()); + } + } + return module_status_.glsl_insts_id; +} + +opt::Instruction* opt::GraphicsRobustAccessPass::GetValueForType( + uint64_t value, const analysis::Integer* type) { + auto* mgr = context()->get_constant_mgr(); + assert(type->width() <= 64); + std::vector words; + words.push_back(uint32_t(value)); + if (type->width() > 32) { + words.push_back(uint32_t(value >> 32u)); + } + const auto* constant = mgr->GetConstant(type, words); + return mgr->GetDefiningInstruction( + constant, context()->get_type_mgr()->GetTypeInstruction(type)); +} + +opt::Instruction* opt::GraphicsRobustAccessPass::WidenInteger( + bool sign_extend, uint32_t bit_width, Instruction* value, + Instruction* before_inst) { + analysis::Integer unsigned_type_for_query(bit_width, false); + auto* type_mgr = context()->get_type_mgr(); + auto* unsigned_type = type_mgr->GetRegisteredType(&unsigned_type_for_query); + auto type_id = context()->get_type_mgr()->GetId(unsigned_type); + auto conversion_id = TakeNextId(); + auto* conversion = InsertInst( + before_inst, (sign_extend ? SpvOpSConvert : SpvOpUConvert), type_id, + conversion_id, {{SPV_OPERAND_TYPE_ID, {value->result_id()}}}); + return conversion; +} + +Instruction* GraphicsRobustAccessPass::MakeClampInst(Instruction* x, + Instruction* min, + Instruction* max, + Instruction* where) { + // Get IDs of instructions we'll be referencing. Evaluate them before calling + // the function so we force a deterministic ordering in case both of them need + // to take a new ID. + const uint32_t glsl_insts_id = GetGlslInsts(); + uint32_t clamp_id = TakeNextId(); + assert(x->type_id() == min->type_id()); + assert(x->type_id() == max->type_id()); + auto* clamp_inst = InsertInst( + where, SpvOpExtInst, x->type_id(), clamp_id, + { + {SPV_OPERAND_TYPE_ID, {glsl_insts_id}}, + {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {GLSLstd450UClamp}}, + {SPV_OPERAND_TYPE_ID, {x->result_id()}}, + {SPV_OPERAND_TYPE_ID, {min->result_id()}}, + {SPV_OPERAND_TYPE_ID, {max->result_id()}}, + }); + return clamp_inst; +} + +Instruction* GraphicsRobustAccessPass::MakeRuntimeArrayLengthInst( + Instruction* access_chain, uint32_t operand_index) { + // The Index parameter to the access chain at |operand_index| is indexing + // *into* the runtime-array. To get the number of elements in the runtime + // array we need a pointer to the Block-decorated struct that contains the + // runtime array. So conceptually we have to go 2 steps backward in the + // access chain. The two steps backward might forces us to traverse backward + // across multiple dominating instructions. + auto* type_mgr = context()->get_type_mgr(); + + // How many access chain indices do we have to unwind to find the pointer + // to the struct containing the runtime array? + uint32_t steps_remaining = 2; + // Find or create an instruction computing the pointer to the structure + // containing the runtime array. + // Walk backward through pointer address calculations until we either get + // to exactly the right base pointer, or to an access chain instruction + // that we can replicate but truncate to compute the address of the right + // struct. + Instruction* current_access_chain = access_chain; + Instruction* pointer_to_containing_struct = nullptr; + while (steps_remaining > 0) { + switch (current_access_chain->opcode()) { + case SpvOpCopyObject: + // Whoops. Walk right through this one. + current_access_chain = + GetDef(current_access_chain->GetSingleWordInOperand(0)); + break; + case SpvOpAccessChain: + case SpvOpInBoundsAccessChain: { + const int first_index_operand = 3; + // How many indices in this access chain contribute to getting us + // to an element in the runtime array? + const auto num_contributing_indices = + current_access_chain == access_chain + ? operand_index - (first_index_operand - 1) + : current_access_chain->NumInOperands() - 1 /* skip the base */; + Instruction* base = + GetDef(current_access_chain->GetSingleWordInOperand(0)); + if (num_contributing_indices == steps_remaining) { + // The base pointer points to the structure. + pointer_to_containing_struct = base; + steps_remaining = 0; + break; + } else if (num_contributing_indices < steps_remaining) { + // Peel off the index and keep going backward. + steps_remaining -= num_contributing_indices; + current_access_chain = base; + } else { + // This access chain has more indices than needed. Generate a new + // access chain instruction, but truncating the list of indices. + const int base_operand = 2; + // We'll use the base pointer and the indices up to but not including + // the one indexing into the runtime array. + Instruction::OperandList ops; + // Use the base pointer + ops.push_back(current_access_chain->GetOperand(base_operand)); + const uint32_t num_indices_to_keep = + num_contributing_indices - steps_remaining - 1; + for (uint32_t i = 0; i <= num_indices_to_keep; i++) { + ops.push_back( + current_access_chain->GetOperand(first_index_operand + i)); + } + // Compute the type of the result of the new access chain. Start at + // the base and walk the indices in a forward direction. + auto* constant_mgr = context()->get_constant_mgr(); + std::vector indices_for_type; + for (uint32_t i = 0; i < ops.size() - 1; i++) { + uint32_t index_for_type_calculation = 0; + Instruction* index = + GetDef(current_access_chain->GetSingleWordOperand( + first_index_operand + i)); + if (auto* index_constant = + constant_mgr->GetConstantFromInst(index)) { + // We only need 32 bits. For the type calculation, it's sufficient + // to take the zero-extended value. It only matters for the struct + // case, and struct member indices are unsigned. + index_for_type_calculation = + uint32_t(index_constant->GetZeroExtendedValue()); + } else { + // Indexing into a variably-sized thing like an array. Use 0. + index_for_type_calculation = 0; + } + indices_for_type.push_back(index_for_type_calculation); + } + auto* base_ptr_type = type_mgr->GetType(base->type_id())->AsPointer(); + auto* base_pointee_type = base_ptr_type->pointee_type(); + auto* new_access_chain_result_pointee_type = + type_mgr->GetMemberType(base_pointee_type, indices_for_type); + const uint32_t new_access_chain_type_id = type_mgr->FindPointerToType( + type_mgr->GetId(new_access_chain_result_pointee_type), + base_ptr_type->storage_class()); + + // Create the instruction and insert it. + const auto new_access_chain_id = TakeNextId(); + auto* new_access_chain = + InsertInst(current_access_chain, current_access_chain->opcode(), + new_access_chain_type_id, new_access_chain_id, ops); + pointer_to_containing_struct = new_access_chain; + steps_remaining = 0; + break; + } + } break; + default: + Fail() << "Unhandled access chain in logical addressing mode passes " + "through " + << current_access_chain->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + return nullptr; + } + } + assert(pointer_to_containing_struct); + auto* pointee_type = + type_mgr->GetType(pointer_to_containing_struct->type_id()) + ->AsPointer() + ->pointee_type(); + + auto* struct_type = pointee_type->AsStruct(); + const uint32_t member_index_of_runtime_array = + uint32_t(struct_type->element_types().size() - 1); + // Create the length-of-array instruction before the original access chain, + // but after the generation of the pointer to the struct. + const auto array_len_id = TakeNextId(); + analysis::Integer uint_type_for_query(32, false); + auto* uint_type = type_mgr->GetRegisteredType(&uint_type_for_query); + auto* array_len = InsertInst( + access_chain, SpvOpArrayLength, type_mgr->GetId(uint_type), array_len_id, + {{SPV_OPERAND_TYPE_ID, {pointer_to_containing_struct->result_id()}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index_of_runtime_array}}}); + return array_len; +} + +spv_result_t GraphicsRobustAccessPass::ClampCoordinateForImageTexelPointer( + opt::Instruction* image_texel_pointer) { + // TODO(dneto): Write tests for this code. + return SPV_SUCCESS; + + // Example: + // %texel_ptr = OpImageTexelPointer %texel_ptr_type %image_ptr %coord + // %sample + // + // We want to clamp %coord components between vector-0 and the result + // of OpImageQuerySize acting on the underlying image. So insert: + // %image = OpLoad %image_type %image_ptr + // %query_size = OpImageQuerySize %query_size_type %image + // + // For a multi-sampled image, %sample is the sample index, and we need + // to clamp it between zero and the number of samples in the image. + // %sample_count = OpImageQuerySamples %uint %image + // %max_sample_index = OpISub %uint %sample_count %uint_1 + // For non-multi-sampled images, the sample index must be constant zero. + + auto* def_use_mgr = context()->get_def_use_mgr(); + auto* type_mgr = context()->get_type_mgr(); + auto* constant_mgr = context()->get_constant_mgr(); + + auto* image_ptr = GetDef(image_texel_pointer->GetSingleWordInOperand(0)); + auto* image_ptr_type = GetDef(image_ptr->type_id()); + auto image_type_id = image_ptr_type->GetSingleWordInOperand(1); + auto* image_type = GetDef(image_type_id); + auto* coord = GetDef(image_texel_pointer->GetSingleWordInOperand(1)); + auto* samples = GetDef(image_texel_pointer->GetSingleWordInOperand(2)); + + // We will modify the module, at least by adding image query instructions. + module_status_.modified = true; + + // Declare the ImageQuery capability if the module doesn't already have it. + auto* feature_mgr = context()->get_feature_mgr(); + if (!feature_mgr->HasCapability(SpvCapabilityImageQuery)) { + auto cap = MakeUnique( + context(), SpvOpCapability, 0, 0, + std::initializer_list{ + {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityImageQuery}}}); + def_use_mgr->AnalyzeInstDefUse(cap.get()); + context()->AddCapability(std::move(cap)); + feature_mgr->Analyze(context()->module()); + } + + // OpImageTexelPointer is used to translate a coordinate and sample index + // into an address for use with an atomic operation. That is, it may only + // used with what Vulkan calls a "storage image" + // (OpTypeImage parameter Sampled=2). + // Note: A storage image never has a level-of-detail associated with it. + + // Constraints on the sample id: + // - Only 2D images can be multi-sampled: OpTypeImage parameter MS=1 + // only if Dim=2D. + // - Non-multi-sampled images (OpTypeImage parameter MS=0) must use + // sample ID to a constant 0. + + // The coordinate is treated as unsigned, and should be clamped against the + // image "size", returned by OpImageQuerySize. (Note: OpImageQuerySizeLod + // is only usable with a sampled image, i.e. its image type has Sampled=1). + + // Determine the result type for the OpImageQuerySize. + // For non-arrayed images: + // non-Cube: + // - Always the same as the coordinate type + // Cube: + // - Use all but the last component of the coordinate (which is the face + // index from 0 to 5). + // For arrayed images (in Vulkan the Dim is 1D, 2D, or Cube): + // non-Cube: + // - A vector with the components in the coordinate, and one more for + // the layer index. + // Cube: + // - The same as the coordinate type: 3-element integer vector. + // - The third component from the size query is the layer count. + // - The third component in the texel pointer calculation is + // 6 * layer + face, where 0 <= face < 6. + // Cube: Use all but the last component of the coordinate (which is the face + // index from 0 to 5). + const auto dim = SpvDim(image_type->GetSingleWordInOperand(1)); + const bool arrayed = image_type->GetSingleWordInOperand(3) == 1; + const bool multisampled = image_type->GetSingleWordInOperand(4) != 0; + const auto query_num_components = [dim, arrayed, this]() -> int { + const int arrayness_bonus = arrayed ? 1 : 0; + int num_coords = 0; + switch (dim) { + case SpvDimBuffer: + case SpvDim1D: + num_coords = 1; + break; + case SpvDimCube: + // For cube, we need bounds for x, y, but not face. + case SpvDimRect: + case SpvDim2D: + num_coords = 2; + break; + case SpvDim3D: + num_coords = 3; + break; + case SpvDimSubpassData: + case SpvDimMax: + return Fail() << "Invalid image dimension for OpImageTexelPointer: " + << int(dim); + break; + } + return num_coords + arrayness_bonus; + }(); + const auto* coord_component_type = [type_mgr, coord]() { + const analysis::Type* coord_type = type_mgr->GetType(coord->type_id()); + if (auto* vector_type = coord_type->AsVector()) { + return vector_type->element_type()->AsInteger(); + } + return coord_type->AsInteger(); + }(); + // For now, only handle 32-bit case for coordinates. + if (!coord_component_type) { + return Fail() << " Coordinates for OpImageTexelPointer are not integral: " + << image_texel_pointer->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + } + if (coord_component_type->width() != 32) { + return Fail() << " Expected OpImageTexelPointer coordinate components to " + "be 32-bits wide. They are " + << coord_component_type->width() << " bits. " + << image_texel_pointer->PrettyPrint( + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + } + const auto* query_size_type = + [type_mgr, coord_component_type, + query_num_components]() -> const analysis::Type* { + if (query_num_components == 1) return coord_component_type; + analysis::Vector proposed(coord_component_type, query_num_components); + return type_mgr->GetRegisteredType(&proposed); + }(); + + const uint32_t image_id = TakeNextId(); + auto* image = + InsertInst(image_texel_pointer, SpvOpLoad, image_type_id, image_id, + {{SPV_OPERAND_TYPE_ID, {image_ptr->result_id()}}}); + + const uint32_t query_size_id = TakeNextId(); + auto* query_size = + InsertInst(image_texel_pointer, SpvOpImageQuerySize, + type_mgr->GetTypeInstruction(query_size_type), query_size_id, + {{SPV_OPERAND_TYPE_ID, {image->result_id()}}}); + + auto* component_1 = constant_mgr->GetConstant(coord_component_type, {1}); + const uint32_t component_1_id = + constant_mgr->GetDefiningInstruction(component_1)->result_id(); + auto* component_0 = constant_mgr->GetConstant(coord_component_type, {0}); + const uint32_t component_0_id = + constant_mgr->GetDefiningInstruction(component_0)->result_id(); + + // If the image is a cube array, then the last component of the queried + // size is the layer count. In the query, we have to accomodate folding + // in the face index ranging from 0 through 5. The inclusive upper bound + // on the third coordinate therefore is multiplied by 6. + auto* query_size_including_faces = query_size; + if (arrayed && (dim == SpvDimCube)) { + // Multiply the last coordinate by 6. + auto* component_6 = constant_mgr->GetConstant(coord_component_type, {6}); + const uint32_t component_6_id = + constant_mgr->GetDefiningInstruction(component_6)->result_id(); + assert(query_num_components == 3); + auto* multiplicand = constant_mgr->GetConstant( + query_size_type, {component_1_id, component_1_id, component_6_id}); + auto* multiplicand_inst = + constant_mgr->GetDefiningInstruction(multiplicand); + const auto query_size_including_faces_id = TakeNextId(); + query_size_including_faces = InsertInst( + image_texel_pointer, SpvOpIMul, + type_mgr->GetTypeInstruction(query_size_type), + query_size_including_faces_id, + {{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}}, + {SPV_OPERAND_TYPE_ID, {multiplicand_inst->result_id()}}}); + } + + // Make a coordinate-type with all 1 components. + auto* coordinate_1 = + query_num_components == 1 + ? component_1 + : constant_mgr->GetConstant( + query_size_type, + std::vector(query_num_components, component_1_id)); + // Make a coordinate-type with all 1 components. + auto* coordinate_0 = + query_num_components == 0 + ? component_0 + : constant_mgr->GetConstant( + query_size_type, + std::vector(query_num_components, component_0_id)); + + const uint32_t query_max_including_faces_id = TakeNextId(); + auto* query_max_including_faces = InsertInst( + image_texel_pointer, SpvOpISub, + type_mgr->GetTypeInstruction(query_size_type), + query_max_including_faces_id, + {{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}}, + {SPV_OPERAND_TYPE_ID, + {constant_mgr->GetDefiningInstruction(coordinate_1)->result_id()}}}); + + // Clamp the coordinate + auto* clamp_coord = + MakeClampInst(coord, constant_mgr->GetDefiningInstruction(coordinate_0), + query_max_including_faces, image_texel_pointer); + image_texel_pointer->SetInOperand(1, {clamp_coord->result_id()}); + + // Clamp the sample index + if (multisampled) { + // Get the sample count via OpImageQuerySamples + const auto query_samples_id = TakeNextId(); + auto* query_samples = InsertInst( + image_texel_pointer, SpvOpImageQuerySamples, + constant_mgr->GetDefiningInstruction(component_0)->type_id(), + query_samples_id, {{SPV_OPERAND_TYPE_ID, {image->result_id()}}}); + + const auto max_samples_id = TakeNextId(); + auto* max_samples = InsertInst(image_texel_pointer, SpvOpImageQuerySamples, + query_samples->type_id(), max_samples_id, + {{SPV_OPERAND_TYPE_ID, {query_samples_id}}, + {SPV_OPERAND_TYPE_ID, {component_1_id}}}); + + auto* clamp_samples = MakeClampInst( + samples, constant_mgr->GetDefiningInstruction(coordinate_0), + max_samples, image_texel_pointer); + image_texel_pointer->SetInOperand(2, {clamp_samples->result_id()}); + + } else { + // Just replace it with 0. Don't even check what was there before. + image_texel_pointer->SetInOperand(2, {component_0_id}); + } + + def_use_mgr->AnalyzeInstUse(image_texel_pointer); + + return SPV_SUCCESS; +} + +opt::Instruction* GraphicsRobustAccessPass::InsertInst( + opt::Instruction* where_inst, SpvOp opcode, uint32_t type_id, + uint32_t result_id, const Instruction::OperandList& operands) { + module_status_.modified = true; + auto* result = where_inst->InsertBefore( + MakeUnique(context(), opcode, type_id, result_id, operands)); + context()->get_def_use_mgr()->AnalyzeInstDefUse(result); + auto* basic_block = context()->get_instr_block(where_inst); + context()->set_instr_block(result, basic_block); + return result; +} + +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h new file mode 100644 index 000000000..d330c9dd1 --- /dev/null +++ b/3rdparty/spirv-tools/source/opt/graphics_robust_access_pass.h @@ -0,0 +1,146 @@ +// 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_GRAPHICS_ROBUST_ACCESS_PASS_H_ +#define SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_ + +#include +#include + +#include "source/diagnostic.h" + +#include "constants.h" +#include "def_use_manager.h" +#include "instruction.h" +#include "module.h" +#include "pass.h" + +namespace spvtools { +namespace opt { + +// See optimizer.hpp for documentation. +class GraphicsRobustAccessPass : public Pass { + public: + GraphicsRobustAccessPass(); + const char* name() const override { return "graphics-robust-access"; } + Status Process() override; + + IRContext::Analysis GetPreservedAnalyses() override { + return IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping | + IRContext::kAnalysisConstants | IRContext::kAnalysisTypes | + IRContext::kAnalysisIdToFuncMapping; + } + + private: + // Records failure for the current module, and returns a stream + // that can be used to provide user error information to the message + // consumer. + spvtools::DiagnosticStream Fail(); + + // Returns SPV_SUCCESS if this pass can correctly process the module. + // Otherwise logs a message and returns a failure code. + spv_result_t IsCompatibleModule(); + + // Transform the current module, if possible. Failure and modification + // status is recorded in the |_| member. On failure, error information is + // posted to the message consumer. The return value has no significance. + spv_result_t ProcessCurrentModule(); + + // Process the given function. Updates the state value |_|. Returns true + // if the module was modified. + bool ProcessAFunction(opt::Function*); + + // Clamps indices in the OpAccessChain or OpInBoundsAccessChain instruction + // |access_chain|. Inserts instructions before the given instruction. Updates + // analyses and records that the module is modified. + void ClampIndicesForAccessChain(Instruction* access_chain); + + // Returns the id of the instruction importing the "GLSL.std.450" extended + // instruction set. If it does not yet exist, the import instruction is + // created and inserted into the module, and updates |_.modified| and + // |_.glsl_insts_id|. + uint32_t GetGlslInsts(); + + // Returns an instruction which is constant with the given value of the given + // type. Ignores any value bits beyond the width of the type. + Instruction* GetValueForType(uint64_t value, const analysis::Integer* type); + + // Returns the unsigned value for the given constant. Assumes it's at most + // 64 bits wide. + uint64_t GetUnsignedValueForConstant(const analysis::Constant* c); + + // Converts an integer value to an unsigned wider integer type, using either + // sign extension or zero extension. The new instruction is inserted + // immediately before |before_inst|, and is analyzed for definitions and uses. + // Returns the newly inserted instruction. Assumes the |value| is an integer + // scalar of a narrower type than |bitwidth| bits. + Instruction* WidenInteger(bool sign_extend, uint32_t bitwidth, + Instruction* value, Instruction* before_inst); + + // Returns a new instruction that invokes the UClamp GLSL.std.450 extended + // instruction with the three given operands. That is, the result of the + // instruction is: + // - |min| if |x| is unsigned-less than |min| + // - |max| if |x| is unsigned-more than |max| + // - |x| otherwise. + // We assume that |min| is unsigned-less-or-equal to |max|, and that the + // operands all have the same scalar integer type. The instruction is + // inserted before |where|. + opt::Instruction* MakeClampInst(Instruction* x, Instruction* min, + Instruction* max, Instruction* where); + + // Returns a new instruction which evaluates to the length the runtime array + // referenced by the access chain at the specfied index. The instruction is + // inserted before the access chain instruction. Returns a null pointer in + // some cases if assumptions are violated (rather than asserting out). + opt::Instruction* MakeRuntimeArrayLengthInst(Instruction* access_chain, + uint32_t operand_index); + + // Clamps the coordinate for an OpImageTexelPointer so it stays within + // the bounds of the size of the image. Updates analyses and records that + // the module is modified. Returns a status code to indicate success + // or failure. If assumptions are not met, returns an error status code + // and emits a diagnostic. + spv_result_t ClampCoordinateForImageTexelPointer(opt::Instruction* itp); + + // Gets the instruction that defines the given id. + opt::Instruction* GetDef(uint32_t id) { + return context()->get_def_use_mgr()->GetDef(id); + } + + // Returns a new instruction inserted before |where_inst|, and created from + // the remaining arguments. Registers the definitions and uses of the new + // instruction and also records its block. + opt::Instruction* InsertInst(opt::Instruction* where_inst, SpvOp opcode, + uint32_t type_id, uint32_t result_id, + const Instruction::OperandList& operands); + + // State required for the current module. + struct PerModuleState { + // This pass modified the module. + bool modified = false; + // True if there is an error processing the current module, e.g. if + // preconditions are not met. + bool failed = false; + // The id of the GLSL.std.450 extended instruction set. Zero if it does + // not exist. + uint32_t glsl_insts_id = 0; + } module_status_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_ diff --git a/3rdparty/spirv-tools/source/opt/instruction.h b/3rdparty/spirv-tools/source/opt/instruction.h index d507d6c3d..d1c4ce148 100644 --- a/3rdparty/spirv-tools/source/opt/instruction.h +++ b/3rdparty/spirv-tools/source/opt/instruction.h @@ -399,7 +399,7 @@ class Instruction : public utils::IntrusiveNodeBase { inline bool operator<(const Instruction&) const; // Takes ownership of the instruction owned by |i| and inserts it immediately - // before |this|. Returns the insterted instruction. + // before |this|. Returns the inserted instruction. Instruction* InsertBefore(std::unique_ptr&& i); // Takes ownership of the instructions in |list| and inserts them in order // immediately before |this|. Returns the first inserted instruction. diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp index 6645a2e3f..00bb918d0 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.cpp @@ -243,10 +243,13 @@ void InstrumentPass::GenStageStreamWriteCode(uint32_t stage_idx, kInstTessEvalOutPrimitiveId, base_offset_id, builder); uint32_t load_id = GenVarLoad( context()->GetBuiltinInputVarId(SpvBuiltInTessCoord), builder); + Instruction* uvec3_cast_inst = + builder->AddUnaryOp(GetVec3UintId(), SpvOpBitcast, load_id); + uint32_t uvec3_cast_id = uvec3_cast_inst->result_id(); Instruction* u_inst = builder->AddIdLiteralOp( - GetUintId(), SpvOpCompositeExtract, load_id, 0); + GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 0); Instruction* v_inst = builder->AddIdLiteralOp( - GetUintId(), SpvOpCompositeExtract, load_id, 1); + GetUintId(), SpvOpCompositeExtract, uvec3_cast_id, 1); GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordU, u_inst->result_id(), builder); GenDebugOutputFieldCode(base_offset_id, kInstTessEvalOutTessCoordV, @@ -552,18 +555,26 @@ uint32_t InstrumentPass::GetUintId() { return uint_id_; } +uint32_t InstrumentPass::GetVecUintId(uint32_t len) { + analysis::TypeManager* type_mgr = context()->get_type_mgr(); + analysis::Integer uint_ty(32, false); + analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty); + analysis::Vector v_uint_ty(reg_uint_ty, len); + analysis::Type* reg_v_uint_ty = type_mgr->GetRegisteredType(&v_uint_ty); + uint32_t v_uint_id = type_mgr->GetTypeInstruction(reg_v_uint_ty); + return v_uint_id; +} + uint32_t InstrumentPass::GetVec4UintId() { - if (v4uint_id_ == 0) { - analysis::TypeManager* type_mgr = context()->get_type_mgr(); - analysis::Integer uint_ty(32, false); - analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty); - analysis::Vector v4uint_ty(reg_uint_ty, 4); - analysis::Type* reg_v4uint_ty = type_mgr->GetRegisteredType(&v4uint_ty); - v4uint_id_ = type_mgr->GetTypeInstruction(reg_v4uint_ty); - } + if (v4uint_id_ == 0) v4uint_id_ = GetVecUintId(4u); return v4uint_id_; } +uint32_t InstrumentPass::GetVec3UintId() { + if (v3uint_id_ == 0) v3uint_id_ = GetVecUintId(3u); + return v3uint_id_; +} + uint32_t InstrumentPass::GetBoolId() { if (bool_id_ == 0) { analysis::TypeManager* type_mgr = context()->get_type_mgr(); @@ -890,6 +901,7 @@ void InstrumentPass::InitializeInstrument() { v4float_id_ = 0; uint_id_ = 0; v4uint_id_ = 0; + v3uint_id_ = 0; bool_id_ = 0; void_id_ = 0; storage_buffer_ext_defined_ = false; diff --git a/3rdparty/spirv-tools/source/opt/instrument_pass.h b/3rdparty/spirv-tools/source/opt/instrument_pass.h index d2556989b..1b3f83e87 100644 --- a/3rdparty/spirv-tools/source/opt/instrument_pass.h +++ b/3rdparty/spirv-tools/source/opt/instrument_pass.h @@ -248,9 +248,15 @@ class InstrumentPass : public Pass { // Return id for v4float type uint32_t GetVec4FloatId(); + // Return id for uint vector type of |length| + uint32_t GetVecUintId(uint32_t length); + // Return id for v4uint type uint32_t GetVec4UintId(); + // Return id for v3uint type + uint32_t GetVec3UintId(); + // Return id for output function. Define if it doesn't exist with // |val_spec_param_cnt| validation-specific uint32 parameters. uint32_t GetStreamWriteFunctionId(uint32_t stage_idx, @@ -366,9 +372,12 @@ class InstrumentPass : public Pass { // id for v4float type uint32_t v4float_id_; - // id for v4float type + // id for v4uint type uint32_t v4uint_id_; + // id for v3uint type + uint32_t v3uint_id_; + // id for 32-bit unsigned type uint32_t uint_id_; diff --git a/3rdparty/spirv-tools/source/opt/ir_context.cpp b/3rdparty/spirv-tools/source/opt/ir_context.cpp index 20309ca52..3e32980f2 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.cpp +++ b/3rdparty/spirv-tools/source/opt/ir_context.cpp @@ -190,6 +190,13 @@ bool IRContext::KillDef(uint32_t id) { } bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) { + return ReplaceAllUsesWithPredicate( + before, after, [](Instruction*, uint32_t) { return true; }); +} + +bool IRContext::ReplaceAllUsesWithPredicate( + uint32_t before, uint32_t after, + const std::function& predicate) { if (before == after) return false; // Ensure that |after| has been registered as def. @@ -198,8 +205,10 @@ bool IRContext::ReplaceAllUsesWith(uint32_t before, uint32_t after) { std::vector> uses_to_update; get_def_use_mgr()->ForEachUse( - before, [&uses_to_update](Instruction* user, uint32_t index) { - uses_to_update.emplace_back(user, index); + before, [&predicate, &uses_to_update](Instruction* user, uint32_t index) { + if (predicate(user, index)) { + uses_to_update.emplace_back(user, index); + } }); Instruction* prev = nullptr; @@ -691,6 +700,13 @@ uint32_t IRContext::GetBuiltinInputVarId(uint32_t builtin) { reg_type = type_mgr->GetRegisteredType(&v3uint_ty); break; } + case SpvBuiltInTessCoord: { + analysis::Float float_ty(32); + analysis::Type* reg_float_ty = type_mgr->GetRegisteredType(&float_ty); + analysis::Vector v3float_ty(reg_float_ty, 3); + reg_type = type_mgr->GetRegisteredType(&v3float_ty); + break; + } default: { assert(false && "unhandled builtin"); return 0; @@ -779,6 +795,42 @@ bool IRContext::ProcessCallTreeFromRoots(ProcessFunction& pfn, return modified; } +void IRContext::EmitErrorMessage(std::string message, Instruction* inst) { + if (!consumer()) { + return; + } + + Instruction* line_inst = inst; + while (line_inst != nullptr) { // Stop at the beginning of the basic block. + if (!line_inst->dbg_line_insts().empty()) { + line_inst = &line_inst->dbg_line_insts().back(); + if (line_inst->opcode() == SpvOpNoLine) { + line_inst = nullptr; + } + break; + } + line_inst = line_inst->PreviousNode(); + } + + uint32_t line_number = 0; + uint32_t col_number = 0; + char* source = nullptr; + if (line_inst != nullptr) { + Instruction* file_name = + get_def_use_mgr()->GetDef(line_inst->GetSingleWordInOperand(0)); + source = reinterpret_cast(&file_name->GetInOperand(0).words[0]); + + // Get the line number and column number. + line_number = line_inst->GetSingleWordInOperand(1); + col_number = line_inst->GetSingleWordInOperand(2); + } + + message += + "\n " + inst->PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES); + consumer()(SPV_MSG_ERROR, source, {line_number, col_number, 0}, + message.c_str()); +} + // Gets the dominator analysis for function |f|. DominatorAnalysis* IRContext::GetDominatorAnalysis(const Function* f) { if (!AreAnalysesValid(kAnalysisDominatorAnalysis)) { diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h index 37c6449c2..05df9c037 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.h +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -382,6 +382,15 @@ class IRContext { // |before| and |after| must be registered definitions in the DefUseManager. bool ReplaceAllUsesWith(uint32_t before, uint32_t after); + // Replace all uses of |before| id with |after| id if those uses + // (instruction, operand pair) return true for |predicate|. Returns true if + // any replacement happens. This method does not kill the definition of the + // |before| id. If |after| is the same as |before|, does nothing and return + // false. + bool ReplaceAllUsesWithPredicate( + uint32_t before, uint32_t after, + const std::function& predicate); + // Returns true if all of the analyses that are suppose to be valid are // actually valid. bool IsConsistent(); @@ -547,6 +556,10 @@ class IRContext { bool ProcessCallTreeFromRoots(ProcessFunction& pfn, std::queue* roots); + // Emmits a error message to the message consumer indicating the error + // described by |message| occurred in |inst|. + void EmitErrorMessage(std::string message, Instruction* inst); + private: // Builds the def-use manager from scratch, even if it was already valid. void BuildDefUseManager() { diff --git a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp index cc1b83746..aebbd000f 100644 --- a/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_single_block_elim_pass.cpp @@ -256,6 +256,7 @@ void LocalSingleBlockLoadStoreElimPass::InitExtensions() { "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_EXT_fragment_invocation_density", + "SPV_EXT_physical_storage_buffer", }); } diff --git a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp index f47777e07..d6beeab29 100644 --- a/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_single_store_elim_pass.cpp @@ -119,6 +119,7 @@ void LocalSingleStoreElimPass::InitExtensionWhiteList() { "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_EXT_fragment_invocation_density", + "SPV_EXT_physical_storage_buffer", }); } bool LocalSingleStoreElimPass::ProcessVariable(Instruction* var_inst) { diff --git a/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp index 299bbe068..c3f4ab658 100644 --- a/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/local_ssa_elim_pass.cpp @@ -105,6 +105,7 @@ void LocalMultiStoreElimPass::InitExtensions() { "SPV_NV_mesh_shader", "SPV_NV_ray_tracing", "SPV_EXT_fragment_invocation_density", + "SPV_EXT_physical_storage_buffer", }); } diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp index b938b2fe9..18c49f5a0 100644 --- a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp @@ -820,12 +820,7 @@ bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) { StructuredCFGAnalysis* struct_cfg_analysis = context()->GetStructuredCFGAnalysis(); - if (struct_cfg_analysis->IsMergeBlock(bb.id())) { - // |bb| must be an empty block ending with OpUnreachable. - if (bb.begin()->opcode() != SpvOpUnreachable) { - return true; - } - } else if (struct_cfg_analysis->IsContinueBlock(bb.id())) { + if (struct_cfg_analysis->IsContinueBlock(bb.id())) { // |bb| must be an empty block ending with a branch to the header. Instruction* inst = &*bb.begin(); if (inst->opcode() != SpvOpBranch) { @@ -836,6 +831,11 @@ bool MergeReturnPass::HasNontrivialUnreachableBlocks(Function* function) { struct_cfg_analysis->ContainingLoop(bb.id())) { return true; } + } else if (struct_cfg_analysis->IsMergeBlock(bb.id())) { + // |bb| must be an empty block ending with OpUnreachable. + if (bb.begin()->opcode() != SpvOpUnreachable) { + return true; + } } else { return true; } diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 5c1e6cabf..f3b56c314 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -22,6 +22,7 @@ #include #include "source/opt/build_module.h" +#include "source/opt/graphics_robust_access_pass.h" #include "source/opt/log.h" #include "source/opt/pass_manager.h" #include "source/opt/passes.h" @@ -314,6 +315,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateCombineAccessChainsPass()); } else if (pass_name == "convert-local-access-chains") { RegisterPass(CreateLocalAccessChainConvertPass()); + } else if (pass_name == "descriptor-scalar-replacement") { + RegisterPass(CreateDescriptorScalarReplacementPass()); } else if (pass_name == "eliminate-dead-code-aggressive") { RegisterPass(CreateAggressiveDCEPass()); } else if (pass_name == "propagate-line-info") { @@ -393,7 +396,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { } else if (pass_name == "replace-invalid-opcode") { RegisterPass(CreateReplaceInvalidOpcodePass()); } else if (pass_name == "inst-bindless-check") { - RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 1)); + RegisterPass(CreateInstBindlessCheckPass(7, 23, true, true, 2)); RegisterPass(CreateSimplificationPass()); RegisterPass(CreateDeadBranchElimPass()); RegisterPass(CreateBlockMergePass()); @@ -472,6 +475,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) { RegisterPass(CreateLegalizeVectorShufflePass()); } else if (pass_name == "decompose-initialized-variables") { RegisterPass(CreateDecomposeInitializedVariablesPass()); + } else if (pass_name == "graphics-robust-access") { + RegisterPass(CreateGraphicsRobustAccessPass()); } else { Errorf(consumer(), nullptr, {}, "Unknown flag '--%s'. Use --help for a list of valid flags", @@ -878,4 +883,14 @@ Optimizer::PassToken CreateSplitInvalidUnreachablePass() { MakeUnique()); } +Optimizer::PassToken CreateGraphicsRobustAccessPass() { + return MakeUnique( + MakeUnique()); +} + +Optimizer::PassToken CreateDescriptorScalarReplacementPass() { + return MakeUnique( + MakeUnique()); +} + } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/opt/passes.h b/3rdparty/spirv-tools/source/opt/passes.h index 0a348e4f5..86588f7eb 100644 --- a/3rdparty/spirv-tools/source/opt/passes.h +++ b/3rdparty/spirv-tools/source/opt/passes.h @@ -29,6 +29,7 @@ #include "source/opt/dead_insert_elim_pass.h" #include "source/opt/dead_variable_elimination.h" #include "source/opt/decompose_initialized_variables_pass.h" +#include "source/opt/desc_sroa.h" #include "source/opt/eliminate_dead_constant_pass.h" #include "source/opt/eliminate_dead_functions_pass.h" #include "source/opt/eliminate_dead_members_pass.h" @@ -37,6 +38,7 @@ #include "source/opt/fold_spec_constant_op_and_composite_pass.h" #include "source/opt/freeze_spec_constant_value_pass.h" #include "source/opt/generate_webgpu_initializers_pass.h" +#include "source/opt/graphics_robust_access_pass.h" #include "source/opt/if_conversion.h" #include "source/opt/inline_exhaustive_pass.h" #include "source/opt/inline_opaque_pass.h" diff --git a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp index 6997bf6b2..d100cb05b 100644 --- a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.cpp @@ -60,25 +60,25 @@ Pass::Status ScalarReplacementPass::ProcessFunction(Function* function) { Instruction* varInst = worklist.front(); worklist.pop(); - if (!ReplaceVariable(varInst, &worklist)) - return Status::Failure; - else - status = Status::SuccessWithChange; + Status var_status = ReplaceVariable(varInst, &worklist); + if (var_status == Status::Failure) + return var_status; + else if (var_status == Status::SuccessWithChange) + status = var_status; } return status; } -bool ScalarReplacementPass::ReplaceVariable( +Pass::Status ScalarReplacementPass::ReplaceVariable( Instruction* inst, std::queue* worklist) { std::vector replacements; if (!CreateReplacementVariables(inst, &replacements)) { - return false; + return Status::Failure; } std::vector dead; - dead.push_back(inst); - if (!get_def_use_mgr()->WhileEachUser( + if (get_def_use_mgr()->WhileEachUser( inst, [this, &replacements, &dead](Instruction* user) { if (!IsAnnotationInst(user->opcode())) { switch (user->opcode()) { @@ -92,8 +92,10 @@ bool ScalarReplacementPass::ReplaceVariable( break; case SpvOpAccessChain: case SpvOpInBoundsAccessChain: - if (!ReplaceAccessChain(user, replacements)) return false; - dead.push_back(user); + if (ReplaceAccessChain(user, replacements)) + dead.push_back(user); + else + return false; break; case SpvOpName: case SpvOpMemberName: @@ -105,7 +107,10 @@ bool ScalarReplacementPass::ReplaceVariable( } return true; })) - return false; + dead.push_back(inst); + + // If there are no dead instructions to clean up, return with no changes. + if (dead.empty()) return Status::SuccessWithoutChange; // Clean up some dead code. while (!dead.empty()) { @@ -125,7 +130,7 @@ bool ScalarReplacementPass::ReplaceVariable( } } - return true; + return Status::SuccessWithChange; } void ScalarReplacementPass::ReplaceWholeLoad( @@ -227,9 +232,14 @@ bool ScalarReplacementPass::ReplaceAccessChain( // indexes) or a direct use of the replacement variable. uint32_t indexId = chain->GetSingleWordInOperand(1u); const Instruction* index = get_def_use_mgr()->GetDef(indexId); - uint64_t indexValue = GetConstantInteger(index); - if (indexValue > replacements.size()) { - // Out of bounds access, this is illegal IR. + int64_t indexValue = context() + ->get_constant_mgr() + ->GetConstantFromInst(index) + ->GetSignExtendedValue(); + if (indexValue < 0 || + indexValue >= static_cast(replacements.size())) { + // Out of bounds access, this is illegal IR. Notice that OpAccessChain + // indexing is 0-based, so we should also reject index == size-of-array. return false; } else { const Instruction* var = replacements[static_cast(indexValue)]; @@ -263,7 +273,7 @@ bool ScalarReplacementPass::CreateReplacementVariables( Instruction* inst, std::vector* replacements) { Instruction* type = GetStorageType(inst); - std::unique_ptr> components_used = + std::unique_ptr> components_used = GetUsedComponents(inst); uint32_t elem = 0; @@ -354,6 +364,35 @@ void ScalarReplacementPass::CreateVariable( get_def_use_mgr()->AnalyzeInstDefUse(inst); context()->set_instr_block(inst, block); + // Copy decorations from the member to the new variable. + Instruction* typeInst = GetStorageType(varInst); + for (auto dec_inst : + get_decoration_mgr()->GetDecorationsFor(typeInst->result_id(), false)) { + uint32_t decoration; + if (dec_inst->opcode() != SpvOpMemberDecorate) { + continue; + } + + if (dec_inst->GetSingleWordInOperand(1) != index) { + continue; + } + + decoration = dec_inst->GetSingleWordInOperand(2u); + switch (decoration) { + case SpvDecorationRelaxedPrecision: { + std::unique_ptr new_dec_inst( + new Instruction(context(), SpvOpDecorate, 0, 0, {})); + new_dec_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {id})); + for (uint32_t i = 2; i < dec_inst->NumInOperandWords(); ++i) { + new_dec_inst->AddOperand(Operand(dec_inst->GetInOperand(i))); + } + context()->AddAnnotationInst(std::move(new_dec_inst)); + } break; + default: + break; + } + } + replacements->push_back(inst); } @@ -461,35 +500,15 @@ void ScalarReplacementPass::GetOrCreateInitialValue(Instruction* source, } } -uint64_t ScalarReplacementPass::GetIntegerLiteral(const Operand& op) const { - assert(op.words.size() <= 2); - uint64_t len = 0; - for (uint32_t i = 0; i != op.words.size(); ++i) { - len |= (op.words[i] << (32 * i)); - } - return len; -} - -uint64_t ScalarReplacementPass::GetConstantInteger( - const Instruction* constant) const { - assert(get_def_use_mgr()->GetDef(constant->type_id())->opcode() == - SpvOpTypeInt); - assert(constant->opcode() == SpvOpConstant || - constant->opcode() == SpvOpConstantNull); - if (constant->opcode() == SpvOpConstantNull) { - return 0; - } - - const Operand& op = constant->GetInOperand(0u); - return GetIntegerLiteral(op); -} - uint64_t ScalarReplacementPass::GetArrayLength( const Instruction* arrayType) const { assert(arrayType->opcode() == SpvOpTypeArray); const Instruction* length = get_def_use_mgr()->GetDef(arrayType->GetSingleWordInOperand(1u)); - return GetConstantInteger(length); + return context() + ->get_constant_mgr() + ->GetConstantFromInst(length) + ->GetZeroExtendedValue(); } uint64_t ScalarReplacementPass::GetNumElements(const Instruction* type) const { @@ -525,25 +544,42 @@ bool ScalarReplacementPass::CanReplaceVariable( assert(varInst->opcode() == SpvOpVariable); // Can only replace function scope variables. - if (varInst->GetSingleWordInOperand(0u) != SpvStorageClassFunction) + if (varInst->GetSingleWordInOperand(0u) != SpvStorageClassFunction) { return false; + } - if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id()))) + if (!CheckTypeAnnotations(get_def_use_mgr()->GetDef(varInst->type_id()))) { return false; + } const Instruction* typeInst = GetStorageType(varInst); - return CheckType(typeInst) && CheckAnnotations(varInst) && CheckUses(varInst); + if (!CheckType(typeInst)) { + return false; + } + + if (!CheckAnnotations(varInst)) { + return false; + } + + if (!CheckUses(varInst)) { + return false; + } + + return true; } bool ScalarReplacementPass::CheckType(const Instruction* typeInst) const { - if (!CheckTypeAnnotations(typeInst)) return false; + if (!CheckTypeAnnotations(typeInst)) { + return false; + } switch (typeInst->opcode()) { case SpvOpTypeStruct: // Don't bother with empty structs or very large structs. if (typeInst->NumInOperands() == 0 || - IsLargerThanSizeLimit(typeInst->NumInOperands())) + IsLargerThanSizeLimit(typeInst->NumInOperands())) { return false; + } return true; case SpvOpTypeArray: if (IsSpecConstant(typeInst->GetSingleWordInOperand(1u))) { @@ -592,6 +628,7 @@ bool ScalarReplacementPass::CheckTypeAnnotations( case SpvDecorationAlignment: case SpvDecorationAlignmentId: case SpvDecorationMaxByteOffset: + case SpvDecorationRelaxedPrecision: break; default: return false; @@ -728,10 +765,10 @@ bool ScalarReplacementPass::IsLargerThanSizeLimit(uint64_t length) const { return length > max_num_elements_; } -std::unique_ptr> +std::unique_ptr> ScalarReplacementPass::GetUsedComponents(Instruction* inst) { - std::unique_ptr> result( - new std::unordered_set()); + std::unique_ptr> result( + new std::unordered_set()); analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr(); @@ -769,18 +806,8 @@ ScalarReplacementPass::GetUsedComponents(Instruction* inst) { const analysis::Constant* index_const = const_mgr->FindDeclaredConstant(index_id); if (index_const) { - const analysis::Integer* index_type = - index_const->type()->AsInteger(); - assert(index_type); - if (index_type->width() == 32) { - result->insert(index_const->GetU32()); - return true; - } else if (index_type->width() == 64) { - result->insert(index_const->GetU64()); - return true; - } - result.reset(nullptr); - return false; + result->insert(index_const->GetSignExtendedValue()); + return true; } else { // Could be any element. Assuming all are used. result.reset(nullptr); diff --git a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h index ca8c0b453..5b5198155 100644 --- a/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h +++ b/3rdparty/spirv-tools/source/opt/scalar_replacement_pass.h @@ -117,9 +117,12 @@ 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 the variable - // could not be replaced. - bool ReplaceVariable(Instruction* inst, std::queue* worklist); + // variable ends up with no uses it is erased. Returns + // - Status::SuccessWithoutChange if the variable could not be replaced. + // - Status::SuccessWithChange if it made replacements. + // - Status::Failure if it couldn't create replacement variables. + Pass::Status ReplaceVariable(Instruction* inst, + std::queue* worklist); // Returns the underlying storage type for |inst|. // @@ -155,14 +158,6 @@ class ScalarReplacementPass : public Pass { bool CreateReplacementVariables(Instruction* inst, std::vector* replacements); - // Returns the value of an OpConstant of integer type. - // - // |constant| must use two or fewer words to generate the value. - uint64_t GetConstantInteger(const Instruction* constant) const; - - // Returns the integer literal for |op|. - uint64_t GetIntegerLiteral(const Operand& op) const; - // Returns the array length for |arrayInst|. uint64_t GetArrayLength(const Instruction* arrayInst) const; @@ -213,7 +208,7 @@ class ScalarReplacementPass : public Pass { // Returns a set containing the which components of the result of |inst| are // potentially used. If the return value is |nullptr|, then every components // is possibly used. - std::unique_ptr> GetUsedComponents( + std::unique_ptr> GetUsedComponents( Instruction* inst); // Returns an instruction defining a null constant with type |type_id|. If diff --git a/3rdparty/spirv-tools/source/opt/simplification_pass.cpp b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp index 6ea4566d5..5780e5da7 100644 --- a/3rdparty/spirv-tools/source/opt/simplification_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp @@ -71,8 +71,16 @@ bool SimplificationPass::SimplifyFunction(Function* function) { } }); if (inst->opcode() == SpvOpCopyObject) { - context()->ReplaceAllUsesWith(inst->result_id(), - inst->GetSingleWordInOperand(0)); + context()->ReplaceAllUsesWithPredicate( + inst->result_id(), inst->GetSingleWordInOperand(0), + [](Instruction* user, uint32_t) { + const auto opcode = user->opcode(); + if (!spvOpcodeIsDebug(opcode) && + !spvOpcodeIsDecoration(opcode)) { + return true; + } + return false; + }); inst_to_kill.insert(inst); in_work_list.insert(inst); } else if (inst->opcode() == SpvOpNop) { @@ -107,8 +115,15 @@ bool SimplificationPass::SimplifyFunction(Function* function) { }); if (inst->opcode() == SpvOpCopyObject) { - context()->ReplaceAllUsesWith(inst->result_id(), - inst->GetSingleWordInOperand(0)); + context()->ReplaceAllUsesWithPredicate( + inst->result_id(), inst->GetSingleWordInOperand(0), + [](Instruction* user, uint32_t) { + const auto opcode = user->opcode(); + if (!spvOpcodeIsDebug(opcode) && !spvOpcodeIsDecoration(opcode)) { + return true; + } + return false; + }); inst_to_kill.insert(inst); in_work_list.insert(inst); } else if (inst->opcode() == SpvOpNop) { diff --git a/3rdparty/spirv-tools/source/opt/types.cpp b/3rdparty/spirv-tools/source/opt/types.cpp index 3717fd1b4..8e2023856 100644 --- a/3rdparty/spirv-tools/source/opt/types.cpp +++ b/3rdparty/spirv-tools/source/opt/types.cpp @@ -274,7 +274,7 @@ void Float::GetExtraHashWords(std::vector* words, words->push_back(width_); } -Vector::Vector(Type* type, uint32_t count) +Vector::Vector(const Type* type, uint32_t count) : Type(kVector), element_type_(type), count_(count) { assert(type->AsBool() || type->AsInteger() || type->AsFloat()); } @@ -299,7 +299,7 @@ void Vector::GetExtraHashWords(std::vector* words, words->push_back(count_); } -Matrix::Matrix(Type* type, uint32_t count) +Matrix::Matrix(const Type* type, uint32_t count) : Type(kMatrix), element_type_(type), count_(count) { assert(type->AsVector()); } @@ -426,7 +426,7 @@ void Array::GetExtraHashWords(std::vector* words, void Array::ReplaceElementType(const Type* type) { element_type_ = type; } -RuntimeArray::RuntimeArray(Type* type) +RuntimeArray::RuntimeArray(const Type* type) : Type(kRuntimeArray), element_type_(type) { assert(!type->AsVoid()); } diff --git a/3rdparty/spirv-tools/source/opt/types.h b/3rdparty/spirv-tools/source/opt/types.h index c997b1fce..1d3552ac3 100644 --- a/3rdparty/spirv-tools/source/opt/types.h +++ b/3rdparty/spirv-tools/source/opt/types.h @@ -258,7 +258,7 @@ class Float : public Type { class Vector : public Type { public: - Vector(Type* element_type, uint32_t count); + Vector(const Type* element_type, uint32_t count); Vector(const Vector&) = default; std::string str() const override; @@ -280,7 +280,7 @@ class Vector : public Type { class Matrix : public Type { public: - Matrix(Type* element_type, uint32_t count); + Matrix(const Type* element_type, uint32_t count); Matrix(const Matrix&) = default; std::string str() const override; @@ -407,7 +407,7 @@ class Array : public Type { class RuntimeArray : public Type { public: - RuntimeArray(Type* element_type); + RuntimeArray(const Type* element_type); RuntimeArray(const RuntimeArray&) = default; std::string str() const override; diff --git a/3rdparty/spirv-tools/source/util/string_utils.h b/3rdparty/spirv-tools/source/util/string_utils.h index f1cd179c9..4282aa949 100644 --- a/3rdparty/spirv-tools/source/util/string_utils.h +++ b/3rdparty/spirv-tools/source/util/string_utils.h @@ -15,8 +15,10 @@ #ifndef SOURCE_UTIL_STRING_UTILS_H_ #define SOURCE_UTIL_STRING_UTILS_H_ +#include #include #include +#include #include "source/util/string_utils.h" @@ -42,6 +44,48 @@ std::string CardinalToOrdinal(size_t cardinal); // string will be empty. std::pair SplitFlagArgs(const std::string& flag); +// Encodes a string as a sequence of words, using the SPIR-V encoding. +inline std::vector MakeVector(std::string input) { + std::vector result; + uint32_t word = 0; + size_t num_bytes = input.size(); + // SPIR-V strings are null-terminated. The byte_index == num_bytes + // case is used to push the terminating null byte. + for (size_t byte_index = 0; byte_index <= num_bytes; byte_index++) { + const auto new_byte = + (byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0)); + word |= (new_byte << (8 * (byte_index % sizeof(uint32_t)))); + if (3 == (byte_index % sizeof(uint32_t))) { + result.push_back(word); + word = 0; + } + } + // Emit a trailing partial word. + if ((num_bytes + 1) % sizeof(uint32_t)) { + result.push_back(word); + } + return result; +} + +// Decode a string from a sequence of words, using the SPIR-V encoding. +template +inline std::string MakeString(const VectorType& words) { + std::string result; + + for (uint32_t word : words) { + for (int byte_index = 0; byte_index < 4; byte_index++) { + uint32_t extracted_word = (word >> (8 * byte_index)) & 0xFF; + char c = static_cast(extracted_word); + if (c == 0) { + return result; + } + result += c; + } + } + assert(false && "Did not find terminating null for the string."); + return result; +} // namespace utils + } // namespace utils } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/val/validate_memory_semantics.cpp b/3rdparty/spirv-tools/source/val/validate_memory_semantics.cpp index 0088cddd7..4c582f09f 100644 --- a/3rdparty/spirv-tools/source/val/validate_memory_semantics.cpp +++ b/3rdparty/spirv-tools/source/val/validate_memory_semantics.cpp @@ -57,36 +57,51 @@ spv_result_t ValidateMemorySemantics(ValidationState_t& _, } if (spvIsWebGPUEnv(_.context()->target_env)) { - uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask | - SpvMemorySemanticsWorkgroupMemoryMask | - SpvMemorySemanticsImageMemoryMask | - SpvMemorySemanticsOutputMemoryKHRMask | - SpvMemorySemanticsMakeAvailableKHRMask | - SpvMemorySemanticsMakeVisibleKHRMask; - if (!spvOpcodeIsAtomicOp(inst->opcode())) { - valid_bits |= SpvMemorySemanticsAcquireReleaseMask; - } + uint32_t valid_bits; + switch (inst->opcode()) { + case SpvOpControlBarrier: + if (!(value & SpvMemorySemanticsAcquireReleaseMask)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU, AcquireRelease must be set for Memory " + "Semantics of OpControlBarrier."; + } - if (value & ~valid_bits) { - if (spvOpcodeIsAtomicOp(inst->opcode())) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "WebGPU spec disallows, for OpAtomic*, any bit masks in " - "Memory Semantics that are not UniformMemory, " - "WorkgroupMemory, ImageMemory, or OutputMemoryKHR"; - } else { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "WebGPU spec disallows any bit masks in Memory Semantics " - "that are not AcquireRelease, UniformMemory, " - "WorkgroupMemory, ImageMemory, OutputMemoryKHR, " - "MakeAvailableKHR, or MakeVisibleKHR"; - } - } + if (!(value & SpvMemorySemanticsWorkgroupMemoryMask)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU, WorkgroupMemory must be set for Memory " + "Semantics of OpControlBarrier."; + } - if (!spvOpcodeIsAtomicOp(inst->opcode()) && - !(value & SpvMemorySemanticsAcquireReleaseMask)) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "WebGPU spec requires AcquireRelease to set in Memory " - "Semantics."; + valid_bits = SpvMemorySemanticsAcquireReleaseMask | + SpvMemorySemanticsWorkgroupMemoryMask; + if (value & ~valid_bits) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU only WorkgroupMemory and AcquireRelease may be " + "set for Memory Semantics of OpControlBarrier."; + } + break; + case SpvOpMemoryBarrier: + if (!(value & SpvMemorySemanticsImageMemoryMask)) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU, ImageMemory must be set for Memory Semantics " + "of OpMemoryBarrier."; + } + valid_bits = SpvMemorySemanticsImageMemoryMask; + if (value & ~valid_bits) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU only ImageMemory may be set for Memory " + "Semantics of OpMemoryBarrier."; + } + break; + default: + if (spvOpcodeIsAtomicOp(inst->opcode())) { + if (value != 0) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "For WebGPU Memory no bits may be set for Memory " + "Semantics of OpAtomic* instructions."; + } + } + break; } } diff --git a/3rdparty/spirv-tools/source/val/validate_scopes.cpp b/3rdparty/spirv-tools/source/val/validate_scopes.cpp index c607984e4..70a65f751 100644 --- a/3rdparty/spirv-tools/source/val/validate_scopes.cpp +++ b/3rdparty/spirv-tools/source/val/validate_scopes.cpp @@ -122,7 +122,6 @@ spv_result_t ValidateExecutionScope(ValidationState_t& _, // WebGPU Specific rules if (spvIsWebGPUEnv(_.context()->target_env)) { - // Scope for execution must be limited to Workgroup or Subgroup if (value != SpvScopeWorkgroup) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) @@ -229,12 +228,41 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, // WebGPU specific rules if (spvIsWebGPUEnv(_.context()->target_env)) { - if (value != SpvScopeWorkgroup && value != SpvScopeInvocation && - value != SpvScopeQueueFamilyKHR) { - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << spvOpcodeString(opcode) - << ": in WebGPU environment Memory Scope is limited to " - << "Workgroup, Invocation, and QueueFamilyKHR"; + switch (inst->opcode()) { + case SpvOpControlBarrier: + if (value != SpvScopeWorkgroup) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in WebGPU environment Memory Scope is limited to " + << "Workgroup for OpControlBarrier"; + } + break; + case SpvOpMemoryBarrier: + if (value != SpvScopeWorkgroup) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in WebGPU environment Memory Scope is limited to " + << "Workgroup for OpMemoryBarrier"; + } + break; + default: + if (spvOpcodeIsAtomicOp(inst->opcode())) { + if (value != SpvScopeQueueFamilyKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in WebGPU environment Memory Scope is limited to " + << "QueueFamilyKHR for OpAtomic* operations"; + } + } + + if (value != SpvScopeWorkgroup && value != SpvScopeInvocation && + value != SpvScopeQueueFamilyKHR) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << spvOpcodeString(opcode) + << ": in WebGPU environment Memory Scope is limited to " + << "Workgroup, Invocation, and QueueFamilyKHR"; + } + break; } } diff --git a/3rdparty/spirv-tools/test/assembly_context_test.cpp b/3rdparty/spirv-tools/test/assembly_context_test.cpp index ee0bb24a9..c8aa06be7 100644 --- a/3rdparty/spirv-tools/test/assembly_context_test.cpp +++ b/3rdparty/spirv-tools/test/assembly_context_test.cpp @@ -17,6 +17,7 @@ #include "gmock/gmock.h" #include "source/instruction.h" +#include "source/util/string_utils.h" #include "test/unit_spirv.h" namespace spvtools { @@ -40,9 +41,8 @@ TEST_P(EncodeStringTest, Sample) { ASSERT_EQ(SPV_SUCCESS, context.binaryEncodeString(GetParam().str.c_str(), &inst)); // We already trust MakeVector - EXPECT_THAT(inst.words, - Eq(Concatenate({GetParam().initial_contents, - spvtest::MakeVector(GetParam().str)}))); + EXPECT_THAT(inst.words, Eq(Concatenate({GetParam().initial_contents, + utils::MakeVector(GetParam().str)}))); } // clang-format off diff --git a/3rdparty/spirv-tools/test/binary_parse_test.cpp b/3rdparty/spirv-tools/test/binary_parse_test.cpp index b9661023e..54664fce7 100644 --- a/3rdparty/spirv-tools/test/binary_parse_test.cpp +++ b/3rdparty/spirv-tools/test/binary_parse_test.cpp @@ -21,6 +21,7 @@ #include "gmock/gmock.h" #include "source/latest_version_opencl_std_header.h" #include "source/table.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -39,7 +40,7 @@ namespace { using ::spvtest::Concatenate; using ::spvtest::MakeInstruction; -using ::spvtest::MakeVector; +using utils::MakeVector; using ::spvtest::ScopedContext; using ::testing::_; using ::testing::AnyOf; diff --git a/3rdparty/spirv-tools/test/comment_test.cpp b/3rdparty/spirv-tools/test/comment_test.cpp index f46b72ac5..49f8df651 100644 --- a/3rdparty/spirv-tools/test/comment_test.cpp +++ b/3rdparty/spirv-tools/test/comment_test.cpp @@ -15,6 +15,7 @@ #include #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -23,7 +24,7 @@ namespace { using spvtest::Concatenate; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using spvtest::TextToBinaryTest; using testing::Eq; diff --git a/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp b/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp index ec012e0ea..9090c2473 100644 --- a/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp +++ b/3rdparty/spirv-tools/test/ext_inst.debuginfo_test.cpp @@ -17,6 +17,7 @@ #include "DebugInfo.h" #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -31,7 +32,7 @@ namespace { using spvtest::Concatenate; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using testing::Eq; struct InstructionCase { diff --git a/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp b/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp index 7dd903e10..7547d9224 100644 --- a/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp +++ b/3rdparty/spirv-tools/test/ext_inst.opencl_test.cpp @@ -17,6 +17,7 @@ #include "gmock/gmock.h" #include "source/latest_version_opencl_std_header.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -25,7 +26,7 @@ namespace { using spvtest::Concatenate; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using spvtest::TextToBinaryTest; using testing::Eq; diff --git a/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt index 9e4066b17..6a101ddcd 100644 --- a/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt @@ -25,10 +25,12 @@ if (${SPIRV_BUILD_FUZZER}) transformation_add_constant_boolean_test.cpp transformation_add_constant_scalar_test.cpp transformation_add_dead_break_test.cpp + transformation_add_dead_continue_test.cpp transformation_add_type_boolean_test.cpp transformation_add_type_float_test.cpp transformation_add_type_int_test.cpp transformation_add_type_pointer_test.cpp + transformation_copy_object_test.cpp transformation_move_block_down_test.cpp transformation_replace_boolean_constant_with_constant_binary_test.cpp transformation_replace_constant_with_uniform_test.cpp diff --git a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.cpp b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.cpp index 545512f9a..e2b951899 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.cpp +++ b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.cpp @@ -79,6 +79,10 @@ bool IsValid(spv_target_env env, const opt::IRContext* ir) { std::string ToString(spv_target_env env, const opt::IRContext* ir) { std::vector binary; ir->module()->ToBinary(&binary, false); + return ToString(env, binary); +} + +std::string ToString(spv_target_env env, const std::vector& binary) { SpirvTools t(env); std::string result; t.Disassemble(binary, &result, kFuzzDisassembleOption); diff --git a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h index 76b7fc292..88a0b205b 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h +++ b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h @@ -17,6 +17,8 @@ #include "gtest/gtest.h" +#include + #include "source/opt/build_module.h" #include "source/opt/ir_context.h" #include "spirv-tools/libspirv.h" @@ -51,6 +53,10 @@ bool IsValid(spv_target_env env, const opt::IRContext* ir); // Useful for debugging. std::string ToString(spv_target_env env, const opt::IRContext* ir); +// Returns the disassembly of the given binary as a string. +// Useful for debugging. +std::string ToString(spv_target_env env, const std::vector& binary); + // Assembly options for writing fuzzer tests. It simplifies matters if // numeric ids do not change. const uint32_t kFuzzAssembleOption = @@ -64,6 +70,29 @@ const spvtools::MessageConsumer kSilentConsumer = [](spv_message_level_t, const char*, const spv_position_t&, const char*) -> void {}; +const spvtools::MessageConsumer kConsoleMessageConsumer = + [](spv_message_level_t level, const char*, const spv_position_t& position, + const char* message) -> void { + switch (level) { + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + std::cerr << "error: line " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_WARNING: + std::cout << "warning: line " << position.index << ": " << message + << std::endl; + break; + case SPV_MSG_INFO: + std::cout << "info: line " << position.index << ": " << message + << std::endl; + break; + default: + break; + } +}; + } // namespace fuzz } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp b/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp index 2cb595a67..550c4fc8d 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp +++ b/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp @@ -33,6 +33,7 @@ void RunFuzzerAndReplayer(const std::string& shader, std::vector binary_in; SpirvTools t(env); + t.SetMessageConsumer(kConsoleMessageConsumer); ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption)); ASSERT_TRUE(t.Validate(binary_in)); @@ -239,9 +240,9 @@ TEST(FuzzerReplayerTest, Miscellaneous1) { OpFunctionEnd )"; - // Do 10 fuzzer runs, starting from an initial seed of 0 (seed value chosen + // Do 5 fuzzer runs, starting from an initial seed of 0 (seed value chosen // arbitrarily). - RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 10); + RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 0, 5); } TEST(FuzzerReplayerTest, Miscellaneous2) { @@ -484,9 +485,9 @@ TEST(FuzzerReplayerTest, Miscellaneous2) { OpFunctionEnd )"; - // Do 10 fuzzer runs, starting from an initial seed of 10 (seed value chosen + // Do 5 fuzzer runs, starting from an initial seed of 10 (seed value chosen // arbitrarily). - RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 10); + RunFuzzerAndReplayer(shader, protobufs::FactSequence(), 10, 5); } TEST(FuzzerReplayerTest, Miscellaneous3) { @@ -969,9 +970,9 @@ TEST(FuzzerReplayerTest, Miscellaneous3) { *facts.mutable_fact()->Add() = temp; } - // Do 10 fuzzer runs, starting from an initial seed of 94 (seed value chosen + // Do 5 fuzzer runs, starting from an initial seed of 94 (seed value chosen // arbitrarily). - RunFuzzerAndReplayer(shader, facts, 94, 10); + RunFuzzerAndReplayer(shader, facts, 94, 5); } } // namespace diff --git a/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp b/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp index 644ace4f0..85f41bc0a 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp +++ b/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp @@ -111,20 +111,21 @@ class InterestingThenRandom : public InterestingnessTest { // |transformation_sequence_in| gets performed with respect to // |interestingness_function|. If |expected_binary_out| is non-empty, it must // match the binary obtained by applying the final shrunk set of -// transformations, in which case the number of such transforations should equal -// |expected_transformations_out_size|. +// transformations, in which case the number of such transformations should +// equal |expected_transformations_out_size|. +// +// The |step_limit| parameter restricts the number of steps that the shrinker +// will try; it can be set to something small for a faster (but less thorough) +// test. void RunAndCheckShrinker( const spv_target_env& target_env, const std::vector& binary_in, const protobufs::FactSequence& initial_facts, const protobufs::TransformationSequence& transformation_sequence_in, const Shrinker::InterestingnessFunction& interestingness_function, const std::vector& expected_binary_out, - uint32_t expected_transformations_out_size) { - // We want tests to complete in reasonable time, so don't go on too long. - const uint32_t kStepLimit = 50; - + uint32_t expected_transformations_out_size, uint32_t step_limit) { // Run the shrinker. - Shrinker shrinker(target_env, kStepLimit); + Shrinker shrinker(target_env, step_limit); shrinker.SetMessageConsumer(kSilentConsumer); std::vector binary_out; @@ -146,14 +147,13 @@ void RunAndCheckShrinker( } } -// Assembles the given |shader| text, and then does the following |num_runs| -// times, with successive seeds starting from |initial_seed|: -// - Runs the fuzzer with the seed to yield a set of transformations +// Assembles the given |shader| text, and then: +// - Runs the fuzzer with |seed| to yield a set of transformations // - Shrinks the transformation with various interestingness functions, // asserting some properties about the result each time void RunFuzzerAndShrinker(const std::string& shader, const protobufs::FactSequence& initial_facts, - uint32_t initial_seed, uint32_t num_runs) { + uint32_t seed) { const auto env = SPV_ENV_UNIVERSAL_1_3; std::vector binary_in; @@ -161,47 +161,50 @@ void RunFuzzerAndShrinker(const std::string& shader, ASSERT_TRUE(t.Assemble(shader, &binary_in, kFuzzAssembleOption)); ASSERT_TRUE(t.Validate(binary_in)); - for (uint32_t seed = initial_seed; seed < initial_seed + num_runs; seed++) { - // Run the fuzzer and check that it successfully yields a valid binary. - std::vector fuzzer_binary_out; - protobufs::TransformationSequence fuzzer_transformation_sequence_out; - spvtools::FuzzerOptions fuzzer_options; - spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed); - Fuzzer fuzzer(env); - fuzzer.SetMessageConsumer(kSilentConsumer); - auto fuzzer_result_status = - fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out, - &fuzzer_transformation_sequence_out); - ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status); - ASSERT_TRUE(t.Validate(fuzzer_binary_out)); + // Run the fuzzer and check that it successfully yields a valid binary. + std::vector fuzzer_binary_out; + protobufs::TransformationSequence fuzzer_transformation_sequence_out; + spvtools::FuzzerOptions fuzzer_options; + spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed); + Fuzzer fuzzer(env); + fuzzer.SetMessageConsumer(kSilentConsumer); + auto fuzzer_result_status = + fuzzer.Run(binary_in, initial_facts, fuzzer_options, &fuzzer_binary_out, + &fuzzer_transformation_sequence_out); + ASSERT_EQ(Fuzzer::FuzzerResultStatus::kComplete, fuzzer_result_status); + ASSERT_TRUE(t.Validate(fuzzer_binary_out)); - // With the AlwaysInteresting test, we should quickly shrink to the original - // binary with no transformations remaining. - RunAndCheckShrinker(env, binary_in, initial_facts, - fuzzer_transformation_sequence_out, - AlwaysInteresting().AsFunction(), binary_in, 0); + const uint32_t kReasonableStepLimit = 50; + const uint32_t kSmallStepLimit = 20; - // With the OnlyInterestingFirstTime test, no shrinking should be achieved. - RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_transformation_sequence_out, - OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out, - static_cast( - fuzzer_transformation_sequence_out.transformation_size())); + // With the AlwaysInteresting test, we should quickly shrink to the original + // binary with no transformations remaining. + RunAndCheckShrinker( + env, binary_in, initial_facts, fuzzer_transformation_sequence_out, + AlwaysInteresting().AsFunction(), binary_in, 0, kReasonableStepLimit); - // The PingPong test is unpredictable; passing an empty expected binary - // means that we don't check anything beyond that shrinking completes - // successfully. - RunAndCheckShrinker(env, binary_in, initial_facts, - fuzzer_transformation_sequence_out, - PingPong().AsFunction(), {}, 0); + // With the OnlyInterestingFirstTime test, no shrinking should be achieved. + RunAndCheckShrinker( + env, binary_in, initial_facts, fuzzer_transformation_sequence_out, + OnlyInterestingFirstTime().AsFunction(), fuzzer_binary_out, + static_cast( + fuzzer_transformation_sequence_out.transformation_size()), + kReasonableStepLimit); - // The InterestingThenRandom test is unpredictable; passing an empty - // expected binary means that we do not check anything about shrinking - // results. - RunAndCheckShrinker( - env, binary_in, initial_facts, fuzzer_transformation_sequence_out, - InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0); - } + // The PingPong test is unpredictable; passing an empty expected binary + // means that we don't check anything beyond that shrinking completes + // successfully. + RunAndCheckShrinker(env, binary_in, initial_facts, + fuzzer_transformation_sequence_out, + PingPong().AsFunction(), {}, 0, kSmallStepLimit); + + // The InterestingThenRandom test is unpredictable; passing an empty + // expected binary means that we do not check anything about shrinking + // results. + RunAndCheckShrinker( + env, binary_in, initial_facts, fuzzer_transformation_sequence_out, + InterestingThenRandom(PseudoRandomGenerator(seed)).AsFunction(), {}, 0, + kSmallStepLimit); } TEST(FuzzerShrinkerTest, Miscellaneous1) { @@ -369,9 +372,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous1) { )"; - // Do 2 fuzz-and-shrink runs, starting from an initial seed of 2 (seed value - // chosen arbitrarily). - RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2, 2); + RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2); } TEST(FuzzerShrinkerTest, Miscellaneous2) { @@ -614,9 +615,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous2) { OpFunctionEnd )"; - // Do 2 fuzzer runs, starting from an initial seed of 19 (seed value chosen - // arbitrarily). - RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19, 2); + RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19); } TEST(FuzzerShrinkerTest, Miscellaneous3) { @@ -1101,7 +1100,7 @@ TEST(FuzzerShrinkerTest, Miscellaneous3) { // Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen // arbitrarily). - RunFuzzerAndShrinker(shader, facts, 194, 2); + RunFuzzerAndShrinker(shader, facts, 194); } } // namespace diff --git a/3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp b/3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp new file mode 100644 index 000000000..573db196e --- /dev/null +++ b/3rdparty/spirv-tools/test/fuzz/transformation_add_dead_continue_test.cpp @@ -0,0 +1,1034 @@ +// 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/fuzz/transformation_add_dead_continue.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationAddDeadContinueTest, SimpleExample) { + // For a simple loop, checks that some dead continue scenarios are possible, + // sanity-checks that some illegal scenarios are indeed not allowed, and then + // applies a transformation. + + // The SPIR-V for this test is adapted from the following GLSL, by separating + // some assignments into their own basic blocks, and adding constants for true + // and false: + // + // void main() { + // int x = 0; + // for (int i = 0; i < 10; i++) { + // x = x + i; + // x = x + i; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %10 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %41 = OpConstantTrue %18 + %42 = OpConstantFalse %18 + %27 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %16 = OpLoad %6 %10 + %19 = OpSLessThan %18 %16 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %20 = OpLoad %6 %8 + %21 = OpLoad %6 %10 + %22 = OpIAdd %6 %20 %21 + OpStore %8 %22 + OpBranch %40 + %40 = OpLabel + %23 = OpLoad %6 %8 + %24 = OpLoad %6 %10 + %25 = OpIAdd %6 %23 %24 + OpStore %8 %25 + OpBranch %14 + %14 = OpLabel + %26 = OpLoad %6 %10 + %28 = OpIAdd %6 %26 %27 + OpStore %10 %28 + OpBranch %11 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager; + + // These are all possibilities. + ASSERT_TRUE(TransformationAddDeadContinue(11, true, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_TRUE(TransformationAddDeadContinue(11, false, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_TRUE(TransformationAddDeadContinue(12, true, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_TRUE(TransformationAddDeadContinue(12, false, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_TRUE(TransformationAddDeadContinue(40, true, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_TRUE(TransformationAddDeadContinue(40, false, {}) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable: 100 is not a block id. + ASSERT_FALSE(TransformationAddDeadContinue(100, true, {}) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable: 10 is not in a loop. + ASSERT_FALSE(TransformationAddDeadContinue(10, true, {}) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable: 15 does not branch unconditionally to a single successor. + ASSERT_FALSE(TransformationAddDeadContinue(15, true, {}) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable: 13 is not in a loop and has no successor. + ASSERT_FALSE(TransformationAddDeadContinue(13, true, {}) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable: 14 is the loop continue target, so it's not OK to jump to + // the loop continue from there. + ASSERT_FALSE(TransformationAddDeadContinue(14, false, {}) + .IsApplicable(context.get(), fact_manager)); + + // These are the transformations we will apply. + auto transformation1 = TransformationAddDeadContinue(11, true, {}); + auto transformation2 = TransformationAddDeadContinue(12, false, {}); + auto transformation3 = TransformationAddDeadContinue(40, true, {}); + + ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager)); + transformation1.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager)); + transformation2.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager)); + transformation3.Apply(context.get(), &fact_manager); + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %10 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %41 = OpConstantTrue %18 + %42 = OpConstantFalse %18 + %27 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + OpLoopMerge %13 %14 None + OpBranchConditional %41 %15 %14 + %15 = OpLabel + %16 = OpLoad %6 %10 + %19 = OpSLessThan %18 %16 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %20 = OpLoad %6 %8 + %21 = OpLoad %6 %10 + %22 = OpIAdd %6 %20 %21 + OpStore %8 %22 + OpBranchConditional %42 %14 %40 + %40 = OpLabel + %23 = OpLoad %6 %8 + %24 = OpLoad %6 %10 + %25 = OpIAdd %6 %23 %24 + OpStore %8 %25 + OpBranchConditional %41 %14 %14 + %14 = OpLabel + %26 = OpLoad %6 %10 + %28 = OpIAdd %6 %26 %27 + OpStore %10 %28 + OpBranch %11 + %13 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationAddDeadContinueTest, + DoNotAllowContinueToMergeBlockOfAnotherLoop) { + // A loop header must dominate its merge block if that merge block is + // reachable. We are thus not allowed to add a dead continue that would result + // in violation of this property. This test checks for such a scenario. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %16 %139 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %8 = OpTypeBool + %14 = OpTypeVector %6 4 + %15 = OpTypePointer Input %14 + %16 = OpVariable %15 Input + %138 = OpTypePointer Output %14 + %139 = OpVariable %138 Output + %400 = OpConstantTrue %8 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %500 + %500 = OpLabel + OpLoopMerge %501 %502 None + OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502 + %503 = OpLabel + OpLoopMerge %502 %504 None + OpBranchConditional %400 %505 %504 + %505 = OpLabel + OpBranch %502 + %504 = OpLabel + OpBranch %503 + %502 = OpLabel + OpBranchConditional %400 %501 %500 + %501 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager; + + ASSERT_FALSE(TransformationAddDeadContinue(500, true, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationAddDeadContinue(500, false, {}) + .IsApplicable(context.get(), fact_manager)); +} + +TEST(TransformationAddDeadContinueTest, DoNotAllowContinueToSelectionMerge) { + // A selection header must dominate its merge block if that merge block is + // reachable. We are thus not allowed to add a dead continue that would result + // in violation of this property. This test checks for such a scenario. + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %16 %139 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %8 = OpTypeBool + %14 = OpTypeVector %6 4 + %15 = OpTypePointer Input %14 + %16 = OpVariable %15 Input + %138 = OpTypePointer Output %14 + %139 = OpVariable %138 Output + %400 = OpConstantTrue %8 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %500 + %500 = OpLabel + OpLoopMerge %501 %502 None + OpBranch %503 ; We are not allowed to change this to OpBranchConditional %400 %503 %502 + %503 = OpLabel + OpSelectionMerge %502 None + OpBranchConditional %400 %505 %504 + %505 = OpLabel + OpBranch %502 + %504 = OpLabel + OpBranch %502 + %502 = OpLabel + OpBranchConditional %400 %501 %500 + %501 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + FactManager fact_manager; + + ASSERT_FALSE(TransformationAddDeadContinue(500, true, {}) + .IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationAddDeadContinue(500, false, {}) + .IsApplicable(context.get(), fact_manager)); +} + +TEST(TransformationAddDeadContinueTest, LoopNest) { + // Checks some allowed and disallowed scenarios for a nest of loops, including + // continuing a loop from an if or switch. + + // The SPIR-V for this test is adapted from the following GLSL: + // + // void main() { + // int x, y; + // do { + // x++; + // for (int j = 0; j < 100; j++) { + // y++; + // if (x == y) { + // x++; + // if (x == 2) { + // y++; + // } + // switch (x) { + // case 0: + // x = 2; + // default: + // break; + // } + // } + // } + // } while (x > y); + // + // for (int i = 0; i < 100; i++) { + // x++; + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %12 "x" + OpName %16 "j" + OpName %27 "y" + OpName %55 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %14 = OpConstant %10 1 + %17 = OpConstant %10 0 + %24 = OpConstant %10 100 + %25 = OpTypeBool + %38 = OpConstant %10 2 + %67 = OpConstantTrue %25 + %68 = OpConstantFalse %25 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %11 Function + %16 = OpVariable %11 Function + %27 = OpVariable %11 Function + %55 = OpVariable %11 Function + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranch %7 + %7 = OpLabel + %13 = OpLoad %10 %12 + %15 = OpIAdd %10 %13 %14 + OpStore %12 %15 + OpStore %16 %17 + OpBranch %18 + %18 = OpLabel + OpLoopMerge %20 %21 None + OpBranch %22 + %22 = OpLabel + %23 = OpLoad %10 %16 + %26 = OpSLessThan %25 %23 %24 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + %28 = OpLoad %10 %27 + %29 = OpIAdd %10 %28 %14 + OpStore %27 %29 + %30 = OpLoad %10 %12 + %31 = OpLoad %10 %27 + %32 = OpIEqual %25 %30 %31 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + %35 = OpLoad %10 %12 + %36 = OpIAdd %10 %35 %14 + OpStore %12 %36 + %37 = OpLoad %10 %12 + %39 = OpIEqual %25 %37 %38 + OpSelectionMerge %41 None + OpBranchConditional %39 %40 %41 + %40 = OpLabel + %42 = OpLoad %10 %27 + %43 = OpIAdd %10 %42 %14 + OpStore %27 %43 + OpBranch %41 + %41 = OpLabel + %44 = OpLoad %10 %12 + OpSelectionMerge %47 None + OpSwitch %44 %46 0 %45 + %46 = OpLabel + OpBranch %47 + %45 = OpLabel + OpStore %12 %38 + OpBranch %46 + %47 = OpLabel + OpBranch %34 + %34 = OpLabel + OpBranch %21 + %21 = OpLabel + %50 = OpLoad %10 %16 + %51 = OpIAdd %10 %50 %14 + OpStore %16 %51 + OpBranch %18 + %20 = OpLabel + OpBranch %9 + %9 = OpLabel + %52 = OpLoad %10 %12 + %53 = OpLoad %10 %27 + %54 = OpSGreaterThan %25 %52 %53 + OpBranchConditional %54 %6 %8 + %8 = OpLabel + OpStore %55 %17 + OpBranch %56 + %56 = OpLabel + OpLoopMerge %58 %59 None + OpBranch %60 + %60 = OpLabel + %61 = OpLoad %10 %55 + %62 = OpSLessThan %25 %61 %24 + OpBranchConditional %62 %57 %58 + %57 = OpLabel + %63 = OpLoad %10 %12 + %64 = OpIAdd %10 %63 %14 + OpStore %12 %64 + OpBranch %59 + %59 = OpLabel + %65 = OpLoad %10 %55 + %66 = OpIAdd %10 %65 %14 + OpStore %55 %66 + OpBranch %56 + %58 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + std::vector good = {6, 7, 18, 20, 34, 40, 45, 46, 47, 56, 57}; + std::vector bad = {5, 8, 9, 19, 21, 22, 33, 41, 58, 59, 60}; + + for (uint32_t from_block : bad) { + ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {}) + .IsApplicable(context.get(), fact_manager)); + } + for (uint32_t from_block : good) { + const TransformationAddDeadContinue transformation(from_block, true, {}); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager)); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %12 "x" + OpName %16 "j" + OpName %27 "y" + OpName %55 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeInt 32 1 + %11 = OpTypePointer Function %10 + %14 = OpConstant %10 1 + %17 = OpConstant %10 0 + %24 = OpConstant %10 100 + %25 = OpTypeBool + %38 = OpConstant %10 2 + %67 = OpConstantTrue %25 + %68 = OpConstantFalse %25 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %11 Function + %16 = OpVariable %11 Function + %27 = OpVariable %11 Function + %55 = OpVariable %11 Function + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranchConditional %67 %7 %9 + %7 = OpLabel + %13 = OpLoad %10 %12 + %15 = OpIAdd %10 %13 %14 + OpStore %12 %15 + OpStore %16 %17 + OpBranchConditional %67 %18 %9 + %18 = OpLabel + OpLoopMerge %20 %21 None + OpBranchConditional %67 %22 %21 + %22 = OpLabel + %23 = OpLoad %10 %16 + %26 = OpSLessThan %25 %23 %24 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + %28 = OpLoad %10 %27 + %29 = OpIAdd %10 %28 %14 + OpStore %27 %29 + %30 = OpLoad %10 %12 + %31 = OpLoad %10 %27 + %32 = OpIEqual %25 %30 %31 + OpSelectionMerge %34 None + OpBranchConditional %32 %33 %34 + %33 = OpLabel + %35 = OpLoad %10 %12 + %36 = OpIAdd %10 %35 %14 + OpStore %12 %36 + %37 = OpLoad %10 %12 + %39 = OpIEqual %25 %37 %38 + OpSelectionMerge %41 None + OpBranchConditional %39 %40 %41 + %40 = OpLabel + %42 = OpLoad %10 %27 + %43 = OpIAdd %10 %42 %14 + OpStore %27 %43 + OpBranchConditional %67 %41 %21 + %41 = OpLabel + %44 = OpLoad %10 %12 + OpSelectionMerge %47 None + OpSwitch %44 %46 0 %45 + %46 = OpLabel + OpBranchConditional %67 %47 %21 + %45 = OpLabel + OpStore %12 %38 + OpBranchConditional %67 %46 %21 + %47 = OpLabel + OpBranchConditional %67 %34 %21 + %34 = OpLabel + OpBranchConditional %67 %21 %21 + %21 = OpLabel + %50 = OpLoad %10 %16 + %51 = OpIAdd %10 %50 %14 + OpStore %16 %51 + OpBranch %18 + %20 = OpLabel + OpBranchConditional %67 %9 %9 + %9 = OpLabel + %52 = OpLoad %10 %12 + %53 = OpLoad %10 %27 + %54 = OpSGreaterThan %25 %52 %53 + OpBranchConditional %54 %6 %8 + %8 = OpLabel + OpStore %55 %17 + OpBranch %56 + %56 = OpLabel + OpLoopMerge %58 %59 None + OpBranchConditional %67 %60 %59 + %60 = OpLabel + %61 = OpLoad %10 %55 + %62 = OpSLessThan %25 %61 %24 + OpBranchConditional %62 %57 %58 + %57 = OpLabel + %63 = OpLoad %10 %12 + %64 = OpIAdd %10 %63 %14 + OpStore %12 %64 + OpBranchConditional %67 %59 %59 + %59 = OpLabel + %65 = OpLoad %10 %55 + %66 = OpIAdd %10 %65 %14 + OpStore %55 %66 + OpBranch %56 + %58 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationAddDeadConditionalTest, LoopInContinueConstruct) { + // Considers some scenarios where there is a loop in a loop's continue + // construct. + + // The SPIR-V for this test is adapted from the following GLSL, with inlining + // applied so that the loop from foo is in the main loop's continue construct: + // + // int foo() { + // int result = 0; + // for (int j = 0; j < 10; j++) { + // result++; + // } + // return result; + // } + // + // void main() { + // for (int i = 0; i < 100; i += foo()) { + // } + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %31 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFunction %6 + %10 = OpTypePointer Function %6 + %12 = OpConstant %6 0 + %20 = OpConstant %6 10 + %21 = OpTypeBool + %100 = OpConstantFalse %21 + %24 = OpConstant %6 1 + %38 = OpConstant %6 100 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %43 = OpVariable %10 Function + %44 = OpVariable %10 Function + %45 = OpVariable %10 Function + %31 = OpVariable %10 Function + OpStore %31 %12 + OpBranch %32 + %32 = OpLabel + OpLoopMerge %34 %35 None + OpBranch %36 + %36 = OpLabel + %37 = OpLoad %6 %31 + %39 = OpSLessThan %21 %37 %38 + OpBranchConditional %39 %33 %34 + %33 = OpLabel + OpBranch %35 + %35 = OpLabel + OpStore %43 %12 + OpStore %44 %12 + OpBranch %46 + %46 = OpLabel + OpLoopMerge %47 %48 None + OpBranch %49 + %49 = OpLabel + %50 = OpLoad %6 %44 + %51 = OpSLessThan %21 %50 %20 + OpBranchConditional %51 %52 %47 + %52 = OpLabel + %53 = OpLoad %6 %43 + OpBranch %101 + %101 = OpLabel + %54 = OpIAdd %6 %53 %24 + OpStore %43 %54 + OpBranch %48 + %48 = OpLabel + %55 = OpLoad %6 %44 + %56 = OpIAdd %6 %55 %24 + OpStore %44 %56 + OpBranch %46 + %47 = OpLabel + %57 = OpLoad %6 %43 + OpStore %45 %57 + %40 = OpLoad %6 %45 + %41 = OpLoad %6 %31 + %42 = OpIAdd %6 %41 %40 + OpStore %31 %42 + OpBranch %32 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + std::vector good = {32, 33, 46, 52, 101}; + std::vector bad = {5, 34, 36, 35, 47, 49, 48}; + + for (uint32_t from_block : bad) { + ASSERT_FALSE(TransformationAddDeadContinue(from_block, false, {}) + .IsApplicable(context.get(), fact_manager)); + } + for (uint32_t from_block : good) { + const TransformationAddDeadContinue transformation(from_block, false, {}); + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + ASSERT_FALSE(transformation.IsApplicable(context.get(), fact_manager)); + } + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %31 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFunction %6 + %10 = OpTypePointer Function %6 + %12 = OpConstant %6 0 + %20 = OpConstant %6 10 + %21 = OpTypeBool + %100 = OpConstantFalse %21 + %24 = OpConstant %6 1 + %38 = OpConstant %6 100 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %43 = OpVariable %10 Function + %44 = OpVariable %10 Function + %45 = OpVariable %10 Function + %31 = OpVariable %10 Function + OpStore %31 %12 + OpBranch %32 + %32 = OpLabel + OpLoopMerge %34 %35 None + OpBranchConditional %100 %35 %36 + %36 = OpLabel + %37 = OpLoad %6 %31 + %39 = OpSLessThan %21 %37 %38 + OpBranchConditional %39 %33 %34 + %33 = OpLabel + OpBranchConditional %100 %35 %35 + %35 = OpLabel + OpStore %43 %12 + OpStore %44 %12 + OpBranch %46 + %46 = OpLabel + OpLoopMerge %47 %48 None + OpBranchConditional %100 %48 %49 + %49 = OpLabel + %50 = OpLoad %6 %44 + %51 = OpSLessThan %21 %50 %20 + OpBranchConditional %51 %52 %47 + %52 = OpLabel + %53 = OpLoad %6 %43 + OpBranchConditional %100 %48 %101 + %101 = OpLabel + %54 = OpIAdd %6 %53 %24 + OpStore %43 %54 + OpBranchConditional %100 %48 %48 + %48 = OpLabel + %55 = OpLoad %6 %44 + %56 = OpIAdd %6 %55 %24 + OpStore %44 %56 + OpBranch %46 + %47 = OpLabel + %57 = OpLoad %6 %43 + OpStore %45 %57 + %40 = OpLoad %6 %45 + %41 = OpLoad %6 %31 + %42 = OpIAdd %6 %41 %40 + OpStore %31 %42 + OpBranch %32 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationAddDeadContinueTest, PhiInstructions) { + // Checks that the transformation works in the presence of phi instructions. + + // The SPIR-V for this test is adapted from the following GLSL, with a bit of + // extra and artificial work to get some interesting uses of OpPhi: + // + // void main() { + // int x; int y; + // float f; + // x = 2; + // f = 3.0; + // if (x > y) { + // x = 3; + // f = 4.0; + // } else { + // x = x + 2; + // f = f + 10.0; + // } + // while (x < y) { + // x = x + 1; + // f = f + 1.0; + // } + // y = x; + // f = f + 3.0; + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %12 "f" + OpName %15 "y" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %10 = OpTypeFloat 32 + %11 = OpTypePointer Function %10 + %13 = OpConstant %10 3 + %17 = OpTypeBool + %80 = OpConstantTrue %17 + %21 = OpConstant %6 3 + %22 = OpConstant %10 4 + %27 = OpConstant %10 10 + %38 = OpConstant %6 1 + %41 = OpConstant %10 1 + %46 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %12 = OpVariable %11 Function + %15 = OpVariable %7 Function + OpStore %8 %9 + OpStore %12 %13 + %18 = OpSGreaterThan %17 %9 %46 + OpSelectionMerge %20 None + OpBranchConditional %18 %19 %23 + %19 = OpLabel + OpStore %8 %21 + OpStore %12 %22 + OpBranch %20 + %23 = OpLabel + %25 = OpIAdd %6 %9 %9 + OpStore %8 %25 + OpBranch %70 + %70 = OpLabel + %28 = OpFAdd %10 %13 %27 + OpStore %12 %28 + OpBranch %20 + %20 = OpLabel + %52 = OpPhi %10 %22 %19 %28 %70 + %48 = OpPhi %6 %21 %19 %25 %70 + OpBranch %29 + %29 = OpLabel + %51 = OpPhi %10 %52 %20 %100 %32 + %47 = OpPhi %6 %48 %20 %101 %32 + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel + %36 = OpSLessThan %17 %47 %46 + OpBranchConditional %36 %30 %31 + %30 = OpLabel + %39 = OpIAdd %6 %47 %38 + OpStore %8 %39 + OpBranch %75 + %75 = OpLabel + %42 = OpFAdd %10 %51 %41 + OpStore %12 %42 + OpBranch %32 + %32 = OpLabel + %100 = OpPhi %10 %42 %75 + %101 = OpPhi %6 %39 %75 + OpBranch %29 + %31 = OpLabel + %71 = OpPhi %6 %47 %33 + %72 = OpPhi %10 %51 %33 + OpStore %15 %71 + %45 = OpFAdd %10 %72 %13 + OpStore %12 %45 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + std::vector bad = {5, 19, 20, 23, 31, 32, 33, 70}; + + std::vector good = {29, 30, 75}; + + for (uint32_t from_block : bad) { + ASSERT_FALSE(TransformationAddDeadContinue(from_block, true, {}) + .IsApplicable(context.get(), fact_manager)); + } + auto transformation1 = TransformationAddDeadContinue(29, true, {13, 21}); + ASSERT_TRUE(transformation1.IsApplicable(context.get(), fact_manager)); + transformation1.Apply(context.get(), &fact_manager); + + auto transformation2 = TransformationAddDeadContinue(30, true, {22, 46}); + ASSERT_TRUE(transformation2.IsApplicable(context.get(), fact_manager)); + transformation2.Apply(context.get(), &fact_manager); + + // 75 already has the continue block as a successor, so we should not provide + // phi ids. + auto transformationBad = TransformationAddDeadContinue(75, true, {27, 46}); + ASSERT_FALSE(transformationBad.IsApplicable(context.get(), fact_manager)); + + auto transformation3 = TransformationAddDeadContinue(75, true, {}); + ASSERT_TRUE(transformation3.IsApplicable(context.get(), fact_manager)); + transformation3.Apply(context.get(), &fact_manager); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "x" + OpName %12 "f" + OpName %15 "y" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 2 + %10 = OpTypeFloat 32 + %11 = OpTypePointer Function %10 + %13 = OpConstant %10 3 + %17 = OpTypeBool + %80 = OpConstantTrue %17 + %21 = OpConstant %6 3 + %22 = OpConstant %10 4 + %27 = OpConstant %10 10 + %38 = OpConstant %6 1 + %41 = OpConstant %10 1 + %46 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %12 = OpVariable %11 Function + %15 = OpVariable %7 Function + OpStore %8 %9 + OpStore %12 %13 + %18 = OpSGreaterThan %17 %9 %46 + OpSelectionMerge %20 None + OpBranchConditional %18 %19 %23 + %19 = OpLabel + OpStore %8 %21 + OpStore %12 %22 + OpBranch %20 + %23 = OpLabel + %25 = OpIAdd %6 %9 %9 + OpStore %8 %25 + OpBranch %70 + %70 = OpLabel + %28 = OpFAdd %10 %13 %27 + OpStore %12 %28 + OpBranch %20 + %20 = OpLabel + %52 = OpPhi %10 %22 %19 %28 %70 + %48 = OpPhi %6 %21 %19 %25 %70 + OpBranch %29 + %29 = OpLabel + %51 = OpPhi %10 %52 %20 %100 %32 + %47 = OpPhi %6 %48 %20 %101 %32 + OpLoopMerge %31 %32 None + OpBranchConditional %80 %33 %32 + %33 = OpLabel + %36 = OpSLessThan %17 %47 %46 + OpBranchConditional %36 %30 %31 + %30 = OpLabel + %39 = OpIAdd %6 %47 %38 + OpStore %8 %39 + OpBranchConditional %80 %75 %32 + %75 = OpLabel + %42 = OpFAdd %10 %51 %41 + OpStore %12 %42 + OpBranchConditional %80 %32 %32 + %32 = OpLabel + %100 = OpPhi %10 %42 %75 %13 %29 %22 %30 + %101 = OpPhi %6 %39 %75 %21 %29 %46 %30 + OpBranch %29 + %31 = OpLabel + %71 = OpPhi %6 %47 %33 + %72 = OpPhi %10 %51 %33 + OpStore %15 %71 + %45 = OpFAdd %10 %72 %13 + OpStore %12 %45 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp b/3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp new file mode 100644 index 000000000..0c214c859 --- /dev/null +++ b/3rdparty/spirv-tools/test/fuzz/transformation_copy_object_test.cpp @@ -0,0 +1,539 @@ +// 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/fuzz/transformation_copy_object.h" +#include "source/fuzz/data_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +TEST(TransformationCopyObjectTest, CopyBooleanConstants) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %8 = OpConstantFalse %6 + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + ASSERT_EQ(0, fact_manager.GetIdsForWhichSynonymsAreKnown().size()); + + TransformationCopyObject copy_true(7, 5, 1, 100); + ASSERT_TRUE(copy_true.IsApplicable(context.get(), fact_manager)); + copy_true.Apply(context.get(), &fact_manager); + + const std::set& ids_for_which_synonyms_are_known = + fact_manager.GetIdsForWhichSynonymsAreKnown(); + ASSERT_EQ(1, ids_for_which_synonyms_are_known.size()); + ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) != + ids_for_which_synonyms_are_known.end()); + ASSERT_EQ(1, fact_manager.GetSynonymsForId(7).size()); + protobufs::DataDescriptor descriptor_100 = MakeDataDescriptor(100, {}); + ASSERT_TRUE(DataDescriptorEquals()(&descriptor_100, + &fact_manager.GetSynonymsForId(7)[0])); + + TransformationCopyObject copy_false(8, 100, 1, 101); + ASSERT_TRUE(copy_false.IsApplicable(context.get(), fact_manager)); + copy_false.Apply(context.get(), &fact_manager); + ASSERT_EQ(2, ids_for_which_synonyms_are_known.size()); + ASSERT_TRUE(ids_for_which_synonyms_are_known.find(8) != + ids_for_which_synonyms_are_known.end()); + ASSERT_EQ(1, fact_manager.GetSynonymsForId(8).size()); + protobufs::DataDescriptor descriptor_101 = MakeDataDescriptor(101, {}); + ASSERT_TRUE(DataDescriptorEquals()(&descriptor_101, + &fact_manager.GetSynonymsForId(8)[0])); + + TransformationCopyObject copy_false_again(101, 5, 3, 102); + ASSERT_TRUE(copy_false_again.IsApplicable(context.get(), fact_manager)); + copy_false_again.Apply(context.get(), &fact_manager); + ASSERT_EQ(3, ids_for_which_synonyms_are_known.size()); + ASSERT_TRUE(ids_for_which_synonyms_are_known.find(101) != + ids_for_which_synonyms_are_known.end()); + ASSERT_EQ(1, fact_manager.GetSynonymsForId(101).size()); + protobufs::DataDescriptor descriptor_102 = MakeDataDescriptor(102, {}); + ASSERT_TRUE(DataDescriptorEquals()(&descriptor_102, + &fact_manager.GetSynonymsForId(101)[0])); + + TransformationCopyObject copy_true_again(7, 102, 1, 103); + ASSERT_TRUE(copy_true_again.IsApplicable(context.get(), fact_manager)); + copy_true_again.Apply(context.get(), &fact_manager); + // This does re-uses an id for which synonyms are already known, so the count + // of such ids does not change. + ASSERT_EQ(3, ids_for_which_synonyms_are_known.size()); + ASSERT_TRUE(ids_for_which_synonyms_are_known.find(7) != + ids_for_which_synonyms_are_known.end()); + ASSERT_EQ(2, fact_manager.GetSynonymsForId(7).size()); + protobufs::DataDescriptor descriptor_103 = MakeDataDescriptor(103, {}); + ASSERT_TRUE(DataDescriptorEquals()(&descriptor_103, + &fact_manager.GetSynonymsForId(7)[0]) || + DataDescriptorEquals()(&descriptor_103, + &fact_manager.GetSynonymsForId(7)[1])); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %6 = OpTypeBool + %7 = OpConstantTrue %6 + %8 = OpConstantFalse %6 + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %100 = OpCopyObject %6 %7 + %101 = OpCopyObject %6 %8 + %102 = OpCopyObject %6 %101 + %103 = OpCopyObject %6 %7 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationCopyObjectTest, CheckIllegalCases) { + // The following SPIR-V comes from this GLSL, pushed through spirv-opt + // and then doctored a bit. + // + // #version 310 es + // + // precision highp float; + // + // struct S { + // int a; + // float b; + // }; + // + // layout(set = 0, binding = 2) uniform block { + // S s; + // lowp float f; + // int ii; + // } ubuf; + // + // layout(location = 0) out vec4 color; + // + // void main() { + // float c = 0.0; + // lowp float d = 0.0; + // S localS = ubuf.s; + // for (int i = 0; i < ubuf.s.a; i++) { + // switch (ubuf.ii) { + // case 0: + // c += 0.1; + // d += 0.2; + // case 1: + // c += 0.1; + // if (c > d) { + // d += 0.2; + // } else { + // d += c; + // } + // break; + // default: + // i += 1; + // localS.b += d; + // } + // } + // color = vec4(c, d, localS.b, 1.0); + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %80 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %12 "S" + OpMemberName %12 0 "a" + OpMemberName %12 1 "b" + OpName %15 "S" + OpMemberName %15 0 "a" + OpMemberName %15 1 "b" + OpName %16 "block" + OpMemberName %16 0 "s" + OpMemberName %16 1 "f" + OpMemberName %16 2 "ii" + OpName %18 "ubuf" + OpName %80 "color" + OpMemberDecorate %12 0 RelaxedPrecision + OpMemberDecorate %15 0 RelaxedPrecision + OpMemberDecorate %15 0 Offset 0 + OpMemberDecorate %15 1 Offset 4 + OpMemberDecorate %16 0 Offset 0 + OpMemberDecorate %16 1 RelaxedPrecision + OpMemberDecorate %16 1 Offset 16 + OpMemberDecorate %16 2 RelaxedPrecision + OpMemberDecorate %16 2 Offset 20 + OpDecorate %16 Block + OpDecorate %18 DescriptorSet 0 + OpDecorate %18 Binding 2 + OpDecorate %38 RelaxedPrecision + OpDecorate %43 RelaxedPrecision + OpDecorate %53 RelaxedPrecision + OpDecorate %62 RelaxedPrecision + OpDecorate %69 RelaxedPrecision + OpDecorate %77 RelaxedPrecision + OpDecorate %80 Location 0 + OpDecorate %101 RelaxedPrecision + OpDecorate %102 RelaxedPrecision + OpDecorate %96 RelaxedPrecision + OpDecorate %108 RelaxedPrecision + OpDecorate %107 RelaxedPrecision + OpDecorate %98 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %9 = OpConstant %6 0 + %11 = OpTypeInt 32 1 + %12 = OpTypeStruct %11 %6 + %15 = OpTypeStruct %11 %6 + %16 = OpTypeStruct %15 %6 %11 + %17 = OpTypePointer Uniform %16 + %18 = OpVariable %17 Uniform + %19 = OpConstant %11 0 + %20 = OpTypePointer Uniform %15 + %27 = OpConstant %11 1 + %36 = OpTypePointer Uniform %11 + %39 = OpTypeBool + %41 = OpConstant %11 2 + %48 = OpConstant %6 0.100000001 + %51 = OpConstant %6 0.200000003 + %78 = OpTypeVector %6 4 + %79 = OpTypePointer Output %78 + %80 = OpVariable %79 Output + %85 = OpConstant %6 1 + %95 = OpUndef %12 + %112 = OpTypePointer Uniform %6 + %113 = OpTypeInt 32 0 + %114 = OpConstant %113 1 + %179 = OpTypePointer Function %39 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %180 = OpVariable %179 Function + %181 = OpVariable %179 Function + %182 = OpVariable %179 Function + %21 = OpAccessChain %20 %18 %19 + %115 = OpAccessChain %112 %21 %114 + %116 = OpLoad %6 %115 + %90 = OpCompositeInsert %12 %116 %95 1 + OpBranch %30 + %30 = OpLabel + %99 = OpPhi %12 %90 %5 %109 %47 + %98 = OpPhi %6 %9 %5 %107 %47 + %97 = OpPhi %6 %9 %5 %105 %47 + %96 = OpPhi %11 %19 %5 %77 %47 + %37 = OpAccessChain %36 %18 %19 %19 + %38 = OpLoad %11 %37 + %40 = OpSLessThan %39 %96 %38 + OpLoopMerge %32 %47 None + OpBranchConditional %40 %31 %32 + %31 = OpLabel + %42 = OpAccessChain %36 %18 %41 + %43 = OpLoad %11 %42 + OpSelectionMerge %47 None + OpSwitch %43 %46 0 %44 1 %45 + %46 = OpLabel + %69 = OpIAdd %11 %96 %27 + %72 = OpCompositeExtract %6 %99 1 + %73 = OpFAdd %6 %72 %98 + %93 = OpCompositeInsert %12 %73 %99 1 + OpBranch %47 + %44 = OpLabel + %50 = OpFAdd %6 %97 %48 + %53 = OpFAdd %6 %98 %51 + OpBranch %45 + %45 = OpLabel + %101 = OpPhi %6 %98 %31 %53 %44 + %100 = OpPhi %6 %97 %31 %50 %44 + %55 = OpFAdd %6 %100 %48 + %58 = OpFOrdGreaterThan %39 %55 %101 + OpSelectionMerge %60 None + OpBranchConditional %58 %59 %63 + %59 = OpLabel + %62 = OpFAdd %6 %101 %51 + OpBranch %60 + %63 = OpLabel + %66 = OpFAdd %6 %101 %55 + OpBranch %60 + %60 = OpLabel + %108 = OpPhi %6 %62 %59 %66 %63 + OpBranch %47 + %47 = OpLabel + %109 = OpPhi %12 %93 %46 %99 %60 + %107 = OpPhi %6 %98 %46 %108 %60 + %105 = OpPhi %6 %97 %46 %55 %60 + %102 = OpPhi %11 %69 %46 %96 %60 + %77 = OpIAdd %11 %102 %27 + OpBranch %30 + %32 = OpLabel + %84 = OpCompositeExtract %6 %99 1 + %86 = OpCompositeConstruct %78 %97 %98 %84 %85 + OpStore %80 %86 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + // Inapplicable because %18 is decorated. + ASSERT_FALSE(TransformationCopyObject(18, 21, 0, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because %77 is decorated. + ASSERT_FALSE(TransformationCopyObject(17, 17, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because %80 is decorated. + ASSERT_FALSE(TransformationCopyObject(80, 77, 0, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because %84 is not available at the requested point + ASSERT_FALSE(TransformationCopyObject(84, 32, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Fine because %84 is available at the requested point + ASSERT_TRUE(TransformationCopyObject(84, 32, 2, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because id %9 is already in use + ASSERT_FALSE(TransformationCopyObject(84, 32, 2, 9) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the requested point is not in a block + ASSERT_FALSE(TransformationCopyObject(84, 86, 3, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because %9 is not in a function + ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because %9 is not in a function + ASSERT_FALSE(TransformationCopyObject(9, 9, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the insert point is right before, or inside, a chunk + // of OpPhis + ASSERT_FALSE(TransformationCopyObject(9, 30, 1, 200) + .IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationCopyObject(9, 99, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // OK, because the insert point is just after a chunk of OpPhis. + ASSERT_TRUE(TransformationCopyObject(9, 96, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the insert point is right after an OpSelectionMerge + ASSERT_FALSE(TransformationCopyObject(9, 58, 2, 200) + .IsApplicable(context.get(), fact_manager)); + + // OK, because the insert point is right before the OpSelectionMerge + ASSERT_TRUE(TransformationCopyObject(9, 58, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the insert point is right after an OpSelectionMerge + ASSERT_FALSE(TransformationCopyObject(9, 43, 2, 200) + .IsApplicable(context.get(), fact_manager)); + + // OK, because the insert point is right before the OpSelectionMerge + ASSERT_TRUE(TransformationCopyObject(9, 43, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the insert point is right after an OpLoopMerge + ASSERT_FALSE(TransformationCopyObject(9, 40, 2, 200) + .IsApplicable(context.get(), fact_manager)); + + // OK, because the insert point is right before the OpLoopMerge + ASSERT_TRUE(TransformationCopyObject(9, 40, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because id %300 does not exist + ASSERT_FALSE(TransformationCopyObject(300, 40, 1, 200) + .IsApplicable(context.get(), fact_manager)); + + // Inapplicable because the following instruction is OpVariable + ASSERT_FALSE(TransformationCopyObject(9, 180, 0, 200) + .IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationCopyObject(9, 181, 0, 200) + .IsApplicable(context.get(), fact_manager)); + ASSERT_FALSE(TransformationCopyObject(9, 182, 0, 200) + .IsApplicable(context.get(), fact_manager)); + + // OK, because this is just past the group of OpVariable instructions. + ASSERT_TRUE(TransformationCopyObject(9, 182, 1, 200) + .IsApplicable(context.get(), fact_manager)); +} + +TEST(TransformationCopyObjectTest, MiscellaneousCopies) { + // The following SPIR-V comes from this GLSL: + // + // #version 310 es + // + // precision highp float; + // + // float g; + // + // vec4 h; + // + // void main() { + // int a; + // int b; + // b = int(g); + // h.x = float(a); + // } + + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "b" + OpName %11 "g" + OpName %16 "h" + OpName %17 "a" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypePointer Private %9 + %11 = OpVariable %10 Private + %14 = OpTypeVector %9 4 + %15 = OpTypePointer Private %14 + %16 = OpVariable %15 Private + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %17 = OpVariable %7 Function + %12 = OpLoad %9 %11 + %13 = OpConvertFToS %6 %12 + OpStore %8 %13 + %18 = OpLoad %6 %17 + %19 = OpConvertSToF %9 %18 + %22 = OpAccessChain %10 %16 %21 + OpStore %22 %19 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + ASSERT_TRUE(IsValid(env, context.get())); + + FactManager fact_manager; + + std::vector transformations = { + TransformationCopyObject(19, 22, 1, 100), + TransformationCopyObject(22, 22, 1, 101), + TransformationCopyObject(12, 22, 1, 102), + TransformationCopyObject(11, 22, 1, 103), + TransformationCopyObject(16, 22, 1, 104), + TransformationCopyObject(8, 22, 1, 105), + TransformationCopyObject(17, 22, 1, 106)}; + + for (auto& transformation : transformations) { + ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager)); + transformation.Apply(context.get(), &fact_manager); + } + + ASSERT_TRUE(IsValid(env, context.get())); + + std::string after_transformation = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %8 "b" + OpName %11 "g" + OpName %16 "h" + OpName %17 "a" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypePointer Private %9 + %11 = OpVariable %10 Private + %14 = OpTypeVector %9 4 + %15 = OpTypePointer Private %14 + %16 = OpVariable %15 Private + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %17 = OpVariable %7 Function + %12 = OpLoad %9 %11 + %13 = OpConvertFToS %6 %12 + OpStore %8 %13 + %18 = OpLoad %6 %17 + %19 = OpConvertSToF %9 %18 + %22 = OpAccessChain %10 %16 %21 + %106 = OpCopyObject %7 %17 + %105 = OpCopyObject %7 %8 + %104 = OpCopyObject %15 %16 + %103 = OpCopyObject %10 %11 + %102 = OpCopyObject %9 %12 + %101 = OpCopyObject %10 %22 + %100 = OpCopyObject %9 %19 + OpStore %22 %19 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/CMakeLists.txt b/3rdparty/spirv-tools/test/opt/CMakeLists.txt index 246c11690..366a61f68 100644 --- a/3rdparty/spirv-tools/test/opt/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/opt/CMakeLists.txt @@ -25,6 +25,7 @@ add_spvtools_unittest(TARGET opt code_sink_test.cpp combine_access_chains_test.cpp compact_ids_test.cpp + constants_test.cpp constant_manager_test.cpp copy_prop_array_test.cpp dead_branch_elim_test.cpp @@ -33,6 +34,7 @@ add_spvtools_unittest(TARGET opt decompose_initialized_variables_test.cpp decoration_manager_test.cpp def_use_test.cpp + desc_sroa_test.cpp eliminate_dead_const_test.cpp eliminate_dead_functions_test.cpp eliminate_dead_member_test.cpp @@ -44,6 +46,7 @@ add_spvtools_unittest(TARGET opt freeze_spec_const_test.cpp function_test.cpp generate_webgpu_initializers_test.cpp + graphics_robust_access_test.cpp if_conversion_test.cpp inline_opaque_test.cpp inline_test.cpp diff --git a/3rdparty/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp b/3rdparty/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp index b4ab10d98..3a7fc2794 100644 --- a/3rdparty/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp +++ b/3rdparty/spirv-tools/test/opt/aggressive_dead_code_elim_test.cpp @@ -6616,6 +6616,152 @@ OpFunctionEnd SinglePassRunAndCheck(spirv, spirv, true); } +TEST_F(AggressiveDCETest, NoEliminateForwardPointer) { + // clang-format off + // + // #version 450 + // #extension GL_EXT_buffer_reference : enable + // + // // forward reference + // layout(buffer_reference) buffer blockType; + // + // layout(buffer_reference, std430, buffer_reference_align = 16) buffer blockType { + // int x; + // blockType next; + // }; + // + // layout(std430) buffer rootBlock { + // blockType root; + // } r; + // + // void main() + // { + // blockType b = r.root; + // b = b.next; + // b.x = 531; + // } + // + // clang-format on + + const std::string predefs1 = + R"(OpCapability Shader +OpCapability PhysicalStorageBufferAddressesEXT +OpExtension "SPV_EXT_physical_storage_buffer" +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel PhysicalStorageBuffer64EXT GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpSource GLSL 450 +OpSourceExtension "GL_EXT_buffer_reference" +)"; + + const std::string names_before = + R"(OpName %main "main" +OpName %blockType "blockType" +OpMemberName %blockType 0 "x" +OpMemberName %blockType 1 "next" +OpName %b "b" +OpName %rootBlock "rootBlock" +OpMemberName %rootBlock 0 "root" +OpName %r "r" +OpMemberDecorate %blockType 0 Offset 0 +OpMemberDecorate %blockType 1 Offset 8 +OpDecorate %blockType Block +OpDecorate %b AliasedPointerEXT +OpMemberDecorate %rootBlock 0 Offset 0 +OpDecorate %rootBlock Block +OpDecorate %r DescriptorSet 0 +OpDecorate %r Binding 0 +)"; + + const std::string names_after = + R"(OpName %main "main" +OpName %blockType "blockType" +OpMemberName %blockType 0 "x" +OpMemberName %blockType 1 "next" +OpName %rootBlock "rootBlock" +OpMemberName %rootBlock 0 "root" +OpName %r "r" +OpMemberDecorate %blockType 0 Offset 0 +OpMemberDecorate %blockType 1 Offset 8 +OpDecorate %blockType Block +OpMemberDecorate %rootBlock 0 Offset 0 +OpDecorate %rootBlock Block +OpDecorate %r DescriptorSet 0 +OpDecorate %r Binding 0 +)"; + + const std::string predefs2_before = + R"(%void = OpTypeVoid +%3 = OpTypeFunction %void +OpTypeForwardPointer %_ptr_PhysicalStorageBufferEXT_blockType PhysicalStorageBufferEXT +%int = OpTypeInt 32 1 +%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBufferEXT_blockType +%_ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %blockType +%_ptr_Function__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer Function %_ptr_PhysicalStorageBufferEXT_blockType +%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBufferEXT_blockType +%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock +%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer +%int_0 = OpConstant %int 0 +%_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBufferEXT_blockType +%int_1 = OpConstant %int 1 +%_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %_ptr_PhysicalStorageBufferEXT_blockType +%int_531 = OpConstant %int 531 +%_ptr_PhysicalStorageBufferEXT_int = OpTypePointer PhysicalStorageBufferEXT %int +)"; + + const std::string predefs2_after = + R"(%void = OpTypeVoid +%8 = OpTypeFunction %void +OpTypeForwardPointer %_ptr_PhysicalStorageBufferEXT_blockType PhysicalStorageBufferEXT +%int = OpTypeInt 32 1 +%blockType = OpTypeStruct %int %_ptr_PhysicalStorageBufferEXT_blockType +%_ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %blockType +%rootBlock = OpTypeStruct %_ptr_PhysicalStorageBufferEXT_blockType +%_ptr_StorageBuffer_rootBlock = OpTypePointer StorageBuffer %rootBlock +%r = OpVariable %_ptr_StorageBuffer_rootBlock StorageBuffer +%int_0 = OpConstant %int 0 +%_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBufferEXT_blockType +%int_1 = OpConstant %int 1 +%_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType = OpTypePointer PhysicalStorageBufferEXT %_ptr_PhysicalStorageBufferEXT_blockType +%int_531 = OpConstant %int 531 +%_ptr_PhysicalStorageBufferEXT_int = OpTypePointer PhysicalStorageBufferEXT %int +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %3 +%5 = OpLabel +%b = OpVariable %_ptr_Function__ptr_PhysicalStorageBufferEXT_blockType Function +%16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType %r %int_0 +%17 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %16 +%21 = OpAccessChain %_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType %17 %int_1 +%22 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %21 Aligned 8 +OpStore %b %22 +%26 = OpAccessChain %_ptr_PhysicalStorageBufferEXT_int %22 %int_0 +OpStore %26 %int_531 Aligned 16 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %8 +%19 = OpLabel +%20 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBufferEXT_blockType %r %int_0 +%21 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %20 +%22 = OpAccessChain %_ptr_PhysicalStorageBufferEXT__ptr_PhysicalStorageBufferEXT_blockType %21 %int_1 +%23 = OpLoad %_ptr_PhysicalStorageBufferEXT_blockType %22 Aligned 8 +%24 = OpAccessChain %_ptr_PhysicalStorageBufferEXT_int %23 %int_0 +OpStore %24 %int_531 Aligned 16 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs1 + names_before + predefs2_before + func_before, + predefs1 + names_after + predefs2_after + func_after, true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Check that logical addressing required diff --git a/3rdparty/spirv-tools/test/opt/constants_test.cpp b/3rdparty/spirv-tools/test/opt/constants_test.cpp new file mode 100644 index 000000000..55c92a513 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/constants_test.cpp @@ -0,0 +1,167 @@ +// 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/constants.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "source/opt/types.h" + +namespace spvtools { +namespace opt { +namespace analysis { +namespace { + +using ConstantTest = ::testing::Test; +using ::testing::ValuesIn; + +template +struct GetExtendedValueCase { + bool is_signed; + int width; + std::vector words; + T expected_value; +}; + +using GetSignExtendedValueCase = GetExtendedValueCase; +using GetZeroExtendedValueCase = GetExtendedValueCase; + +using GetSignExtendedValueTest = + ::testing::TestWithParam; +using GetZeroExtendedValueTest = + ::testing::TestWithParam; + +TEST_P(GetSignExtendedValueTest, Case) { + Integer type(GetParam().width, GetParam().is_signed); + IntConstant value(&type, GetParam().words); + + EXPECT_EQ(GetParam().expected_value, value.GetSignExtendedValue()); +} + +TEST_P(GetZeroExtendedValueTest, Case) { + Integer type(GetParam().width, GetParam().is_signed); + IntConstant value(&type, GetParam().words); + + EXPECT_EQ(GetParam().expected_value, value.GetZeroExtendedValue()); +} + +const uint32_t k32ones = ~uint32_t(0); +const uint64_t k64ones = ~uint64_t(0); +const int64_t kSBillion = 1000 * 1000 * 1000; +const uint64_t kUBillion = 1000 * 1000 * 1000; + +INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetSignExtendedValueTest, + ValuesIn(std::vector{ + // 4 bits + {false, 4, {0}, 0}, + {false, 4, {7}, 7}, + {false, 4, {15}, 15}, + {true, 4, {0}, 0}, + {true, 4, {7}, 7}, + {true, 4, {0xfffffff8}, -8}, + {true, 4, {k32ones}, -1}, + // 16 bits + {false, 16, {0}, 0}, + {false, 16, {32767}, 32767}, + {false, 16, {32768}, 32768}, + {false, 16, {65000}, 65000}, + {true, 16, {0}, 0}, + {true, 16, {32767}, 32767}, + {true, 16, {0xfffffff8}, -8}, + {true, 16, {k32ones}, -1}, + // 32 bits + {false, 32, {0}, 0}, + {false, 32, {1000000}, 1000000}, + {true, 32, {0xfffffff8}, -8}, + {true, 32, {k32ones}, -1}, + })); + +INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetSignExtendedValueTest, + ValuesIn(std::vector{ + // 48 bits + {false, 48, {0, 0}, 0}, + {false, 48, {5, 0}, 5}, + {false, 48, {0xfffffff8, k32ones}, -8}, + {false, 48, {k32ones, k32ones}, -1}, + {false, 48, {0xdcd65000, 1}, 8 * kSBillion}, + {true, 48, {0xfffffff8, k32ones}, -8}, + {true, 48, {k32ones, k32ones}, -1}, + {true, 48, {0xdcd65000, 1}, 8 * kSBillion}, + + // 64 bits + {false, 64, {12, 0}, 12}, + {false, 64, {0xdcd65000, 1}, 8 * kSBillion}, + {false, 48, {0xfffffff8, k32ones}, -8}, + {false, 64, {k32ones, k32ones}, -1}, + {true, 64, {12, 0}, 12}, + {true, 64, {0xdcd65000, 1}, 8 * kSBillion}, + {true, 48, {0xfffffff8, k32ones}, -8}, + {true, 64, {k32ones, k32ones}, -1}, + })); + +INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetZeroExtendedValueTest, + ValuesIn(std::vector{ + // 4 bits + {false, 4, {0}, 0}, + {false, 4, {7}, 7}, + {false, 4, {15}, 15}, + {true, 4, {0}, 0}, + {true, 4, {7}, 7}, + {true, 4, {0xfffffff8}, 0xfffffff8}, + {true, 4, {k32ones}, k32ones}, + // 16 bits + {false, 16, {0}, 0}, + {false, 16, {32767}, 32767}, + {false, 16, {32768}, 32768}, + {false, 16, {65000}, 65000}, + {true, 16, {0}, 0}, + {true, 16, {32767}, 32767}, + {true, 16, {0xfffffff8}, 0xfffffff8}, + {true, 16, {k32ones}, k32ones}, + // 32 bits + {false, 32, {0}, 0}, + {false, 32, {1000000}, 1000000}, + {true, 32, {0xfffffff8}, 0xfffffff8}, + {true, 32, {k32ones}, k32ones}, + })); + +INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetZeroExtendedValueTest, + ValuesIn(std::vector{ + // 48 bits + {false, 48, {0, 0}, 0}, + {false, 48, {5, 0}, 5}, + {false, 48, {0xfffffff8, k32ones}, uint64_t(-8)}, + {false, 48, {k32ones, k32ones}, uint64_t(-1)}, + {false, 48, {0xdcd65000, 1}, 8 * kUBillion}, + {true, 48, {0xfffffff8, k32ones}, uint64_t(-8)}, + {true, 48, {k32ones, k32ones}, uint64_t(-1)}, + {true, 48, {0xdcd65000, 1}, 8 * kUBillion}, + + // 64 bits + {false, 64, {12, 0}, 12}, + {false, 64, {0xdcd65000, 1}, 8 * kUBillion}, + {false, 48, {0xfffffff8, k32ones}, uint64_t(-8)}, + {false, 64, {k32ones, k32ones}, k64ones}, + {true, 64, {12, 0}, 12}, + {true, 64, {0xdcd65000, 1}, 8 * kUBillion}, + {true, 48, {0xfffffff8, k32ones}, uint64_t(-8)}, + {true, 64, {k32ones, k32ones}, k64ones}, + })); + +} // namespace +} // namespace analysis +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp index 3eb3ef58e..fcfbff060 100644 --- a/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp +++ b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp @@ -22,6 +22,7 @@ #include "source/opt/decoration_manager.h" #include "source/opt/ir_context.h" #include "source/spirv_constant.h" +#include "source/util/string_utils.h" #include "test/unit_spirv.h" namespace spvtools { @@ -29,7 +30,7 @@ namespace opt { namespace analysis { namespace { -using spvtest::MakeVector; +using utils::MakeVector; class DecorationManagerTest : public ::testing::Test { public: diff --git a/3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp b/3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp new file mode 100644 index 000000000..04ea0f736 --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/desc_sroa_test.cpp @@ -0,0 +1,209 @@ +// 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 + +#include "gmock/gmock.h" +#include "test/opt/assembly_builder.h" +#include "test/opt/pass_fixture.h" +#include "test/opt/pass_utils.h" + +namespace spvtools { +namespace opt { +namespace { + +using DescriptorScalarReplacementTest = PassTest<::testing::Test>; + +TEST_F(DescriptorScalarReplacementTest, ExpandTexture) { + const std::string text = R"( +; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var1]] Binding 0 +; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var2]] Binding 1 +; CHECK: OpDecorate [[var3:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var3]] Binding 2 +; CHECK: OpDecorate [[var4:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var4]] Binding 3 +; CHECK: OpDecorate [[var5:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var5]] Binding 4 +; CHECK: [[image_type:%\w+]] = OpTypeImage +; CHECK: [[ptr_type:%\w+]] = OpTypePointer UniformConstant [[image_type]] +; CHECK: [[var1]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var2]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var3]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var4]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var5]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: OpLoad [[image_type]] [[var1]] +; CHECK: OpLoad [[image_type]] [[var2]] +; CHECK: OpLoad [[image_type]] [[var3]] +; CHECK: OpLoad [[image_type]] [[var4]] +; CHECK: OpLoad [[image_type]] [[var5]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpDecorate %MyTextures DescriptorSet 0 + OpDecorate %MyTextures Binding 0 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %int_3 = OpConstant %int 3 + %int_4 = OpConstant %int 4 + %uint = OpTypeInt 32 0 + %uint_5 = OpConstant %uint 5 + %float = OpTypeFloat 32 +%type_2d_image = OpTypeImage %float 2D 2 0 0 1 Unknown +%_arr_type_2d_image_uint_5 = OpTypeArray %type_2d_image %uint_5 +%_ptr_UniformConstant__arr_type_2d_image_uint_5 = OpTypePointer UniformConstant %_arr_type_2d_image_uint_5 + %v2float = OpTypeVector %float 2 + %void = OpTypeVoid + %26 = OpTypeFunction %void +%_ptr_UniformConstant_type_2d_image = OpTypePointer UniformConstant %type_2d_image + %MyTextures = OpVariable %_ptr_UniformConstant__arr_type_2d_image_uint_5 UniformConstant + %main = OpFunction %void None %26 + %28 = OpLabel + %29 = OpUndef %v2float + %30 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_0 + %31 = OpLoad %type_2d_image %30 + %35 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_1 + %36 = OpLoad %type_2d_image %35 + %40 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_2 + %41 = OpLoad %type_2d_image %40 + %45 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_3 + %46 = OpLoad %type_2d_image %45 + %50 = OpAccessChain %_ptr_UniformConstant_type_2d_image %MyTextures %int_4 + %51 = OpLoad %type_2d_image %50 + OpReturn + OpFunctionEnd + + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DescriptorScalarReplacementTest, ExpandSampler) { + const std::string text = R"( +; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var1]] Binding 1 +; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var2]] Binding 2 +; CHECK: OpDecorate [[var3:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var3]] Binding 3 +; CHECK: [[sampler_type:%\w+]] = OpTypeSampler +; CHECK: [[ptr_type:%\w+]] = OpTypePointer UniformConstant [[sampler_type]] +; CHECK: [[var1]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var2]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: [[var3]] = OpVariable [[ptr_type]] UniformConstant +; CHECK: OpLoad [[sampler_type]] [[var1]] +; CHECK: OpLoad [[sampler_type]] [[var2]] +; CHECK: OpLoad [[sampler_type]] [[var3]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpDecorate %MySampler DescriptorSet 0 + OpDecorate %MySampler Binding 1 + %int = OpTypeInt 32 1 + %int_0 = OpConstant %int 0 + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %uint = OpTypeInt 32 0 + %uint_3 = OpConstant %uint 3 +%type_sampler = OpTypeSampler +%_arr_type_sampler_uint_3 = OpTypeArray %type_sampler %uint_3 +%_ptr_UniformConstant__arr_type_sampler_uint_3 = OpTypePointer UniformConstant %_arr_type_sampler_uint_3 + %void = OpTypeVoid + %26 = OpTypeFunction %void +%_ptr_UniformConstant_type_sampler = OpTypePointer UniformConstant %type_sampler + %MySampler = OpVariable %_ptr_UniformConstant__arr_type_sampler_uint_3 UniformConstant + %main = OpFunction %void None %26 + %28 = OpLabel + %31 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_0 + %32 = OpLoad %type_sampler %31 + %35 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_1 + %36 = OpLoad %type_sampler %35 + %40 = OpAccessChain %_ptr_UniformConstant_type_sampler %MySampler %int_2 + %41 = OpLoad %type_sampler %40 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +TEST_F(DescriptorScalarReplacementTest, ExpandSSBO) { + // Tests the expansion of an SSBO. Also check that an access chain with more + // than 1 index is correctly handled. + const std::string text = R"( +; CHECK: OpDecorate [[var1:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var1]] Binding 0 +; CHECK: OpDecorate [[var2:%\w+]] DescriptorSet 0 +; CHECK: OpDecorate [[var2]] Binding 1 +; CHECK: OpTypeStruct +; CHECK: [[struct_type:%\w+]] = OpTypeStruct +; CHECK: [[ptr_type:%\w+]] = OpTypePointer Uniform [[struct_type]] +; CHECK: [[var1]] = OpVariable [[ptr_type]] Uniform +; CHECK: [[var2]] = OpVariable [[ptr_type]] Uniform +; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[var1]] %uint_0 %uint_0 %uint_0 +; CHECK: OpLoad %v4float [[ac1]] +; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[var2]] %uint_0 %uint_0 %uint_0 +; CHECK: OpLoad %v4float [[ac2]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" + OpExecutionMode %main OriginUpperLeft + OpSource HLSL 600 + OpDecorate %buffers DescriptorSet 0 + OpDecorate %buffers Binding 0 + OpMemberDecorate %S 0 Offset 0 + OpDecorate %_runtimearr_S ArrayStride 16 + OpMemberDecorate %type_StructuredBuffer_S 0 Offset 0 + OpMemberDecorate %type_StructuredBuffer_S 0 NonWritable + OpDecorate %type_StructuredBuffer_S BufferBlock + %uint = OpTypeInt 32 0 + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %float = OpTypeFloat 32 + %v4float = OpTypeVector %float 4 + %S = OpTypeStruct %v4float +%_runtimearr_S = OpTypeRuntimeArray %S +%type_StructuredBuffer_S = OpTypeStruct %_runtimearr_S +%_arr_type_StructuredBuffer_S_uint_2 = OpTypeArray %type_StructuredBuffer_S %uint_2 +%_ptr_Uniform__arr_type_StructuredBuffer_S_uint_2 = OpTypePointer Uniform %_arr_type_StructuredBuffer_S_uint_2 +%_ptr_Uniform_type_StructuredBuffer_S = OpTypePointer Uniform %type_StructuredBuffer_S + %void = OpTypeVoid + %19 = OpTypeFunction %void +%_ptr_Uniform_v4float = OpTypePointer Uniform %v4float + %buffers = OpVariable %_ptr_Uniform__arr_type_StructuredBuffer_S_uint_2 Uniform + %main = OpFunction %void None %19 + %21 = OpLabel + %22 = OpAccessChain %_ptr_Uniform_v4float %buffers %uint_0 %uint_0 %uint_0 %uint_0 + %23 = OpLoad %v4float %22 + %24 = OpAccessChain %_ptr_Uniform_type_StructuredBuffer_S %buffers %uint_1 + %25 = OpAccessChain %_ptr_Uniform_v4float %24 %uint_0 %uint_0 %uint_0 + %26 = OpLoad %v4float %25 + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true); +} + +} // namespace +} // namespace opt +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/fold_test.cpp b/3rdparty/spirv-tools/test/opt/fold_test.cpp index 3ea320463..a9f30890f 100644 --- a/3rdparty/spirv-tools/test/opt/fold_test.cpp +++ b/3rdparty/spirv-tools/test/opt/fold_test.cpp @@ -11,6 +11,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include "source/opt/fold.h" + #include #include #include @@ -22,7 +24,6 @@ #include "gtest/gtest.h" #include "source/opt/build_module.h" #include "source/opt/def_use_manager.h" -#include "source/opt/fold.h" #include "source/opt/ir_context.h" #include "source/opt/module.h" #include "spirv-tools/libspirv.hpp" @@ -2980,7 +2981,17 @@ INSTANTIATE_TEST_SUITE_P(CompositeExtractFoldingTest, GeneralInstructionFoldingT "%4 = OpCompositeExtract %int %3 0\n" + "OpReturn\n" + "OpFunctionEnd", - 4, INT_7_ID) + 4, INT_7_ID), + // Test case 13: https://github.com/KhronosGroup/SPIRV-Tools/issues/2608 + // Out of bounds access. Do not fold. + InstructionFoldingCase( + Header() + "%main = OpFunction %void None %void_func\n" + + "%main_lab = OpLabel\n" + + "%2 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1\n" + + "%3 = OpCompositeExtract %float %2 4\n" + + "OpReturn\n" + + "OpFunctionEnd", + 3, 0) )); INSTANTIATE_TEST_SUITE_P(CompositeConstructFoldingTest, GeneralInstructionFoldingTest, diff --git a/3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp b/3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp new file mode 100644 index 000000000..137d0e89a --- /dev/null +++ b/3rdparty/spirv-tools/test/opt/graphics_robust_access_test.cpp @@ -0,0 +1,1307 @@ +// 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 +#include +#include +#include + +#include "gmock/gmock.h" +#include "pass_fixture.h" +#include "pass_utils.h" +#include "source/opt/graphics_robust_access_pass.h" + +namespace { + +using namespace spvtools; + +using opt::GraphicsRobustAccessPass; +using GraphicsRobustAccessTest = opt::PassTest<::testing::Test>; + +// Test incompatible module, determined at module-level. + +TEST_F(GraphicsRobustAccessTest, FailNotShader) { + const std::string text = R"( +; CHECK: Can only process Shader modules +OpCapability Kernel +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointers) { + const std::string text = R"( +; CHECK: Can't process modules with VariablePointers capability +OpCapability VariablePointers +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointersStorageBuffer) { + const std::string text = R"( +; CHECK: Can't process modules with VariablePointersStorageBuffer capability +OpCapability VariablePointersStorageBuffer +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, FailCantProcessRuntimeDescriptorArrayEXT) { + const std::string text = R"( +; CHECK: Can't process modules with RuntimeDescriptorArrayEXT capability +OpCapability RuntimeDescriptorArrayEXT +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical32AddressingModel) { + const std::string text = R"( +; CHECK: Addressing model must be Logical. Found OpMemoryModel Physical32 OpenCL +OpCapability Shader +OpMemoryModel Physical32 OpenCL +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical64AddressingModel) { + const std::string text = R"( +; CHECK: Addressing model must be Logical. Found OpMemoryModel Physical64 OpenCL +OpCapability Shader +OpMemoryModel Physical64 OpenCL +)"; + + SinglePassRunAndFail(text); +} + +TEST_F(GraphicsRobustAccessTest, + FailCantProcessPhysicalStorageBuffer64EXTAddressingModel) { + const std::string text = R"( +; CHECK: Addressing model must be Logical. Found OpMemoryModel PhysicalStorageBuffer64EXT GLSL450 +OpCapability Shader +OpMemoryModel PhysicalStorageBuffer64EXT GLSL450 +)"; + + SinglePassRunAndFail(text); +} + +// Test access chains + +// Returns the names of access chain instructions handled by the pass. +// For the purposes of this pass, regular and in-bounds access chains are the +// same.) +std::vector AccessChains() { + return {"OpAccessChain", "OpInBoundsAccessChain"}; +} + +std::string ShaderPreamble() { + return R"( + OpCapability Shader + OpMemoryModel Logical Simple + OpEntryPoint GLCompute %main "main" +)"; +} + +std::string ShaderPreamble(const std::vector& names) { + std::ostringstream os; + os << ShaderPreamble(); + for (auto& name : names) { + os << " OpName %" << name << " \"" << name << "\"\n"; + } + return os.str(); +} + +std::string ShaderPreambleAC() { + return ShaderPreamble({"ac", "ptr_ty", "var"}); +} + +std::string ShaderPreambleAC(const std::vector& names) { + auto names2 = names; + names2.push_back("ac"); + names2.push_back("ptr_ty"); + names2.push_back("var"); + return ShaderPreamble(names2); +} + +std::string DecoSSBO() { + return R"( + OpDecorate %ssbo_s BufferBlock + OpMemberDecorate %ssbo_s 0 Offset 0 + OpMemberDecorate %ssbo_s 1 Offset 4 + OpMemberDecorate %ssbo_s 2 Offset 16 + OpDecorate %var DescriptorSet 0 + OpDecorate %var Binding 0 +)"; +} + +std::string TypesVoid() { + return R"( + %void = OpTypeVoid + %void_fn = OpTypeFunction %void +)"; +} + +std::string TypesInt() { + return R"( + %uint = OpTypeInt 32 0 + %int = OpTypeInt 32 1 +)"; +} + +std::string TypesFloat() { + return R"( + %float = OpTypeFloat 32 +)"; +} + +std::string TypesShort() { + return R"( + %ushort = OpTypeInt 16 0 + %short = OpTypeInt 16 1 +)"; +} + +std::string TypesLong() { + return R"( + %ulong = OpTypeInt 64 0 + %long = OpTypeInt 64 1 +)"; +} + +std::string MainPrefix() { + return R"( + %main = OpFunction %void None %void_fn + %entry = OpLabel +)"; +} + +std::string MainSuffix() { + return R"( + OpReturn + OpFunctionEnd +)"; +} + +std::string ACCheck(const std::string& access_chain_inst, + const std::string& original, + const std::string& transformed) { + return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" + + (transformed.size() ? " " : "") + transformed + + "\n ; CHECK-NOT: " + access_chain_inst + + "\n ; CHECK-NEXT: OpReturn" + "\n %ac = " + + access_chain_inst + " %ptr_ty %var " + (original.size() ? " " : "") + + original + "\n"; +} + +std::string ACCheckFail(const std::string& access_chain_inst, + const std::string& original, + const std::string& transformed) { + return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" + + (transformed.size() ? " " : "") + transformed + + "\n ; CHECK-NOT: " + access_chain_inst + + "\n ; CHECK-NOT: OpReturn" + "\n %ac = " + + access_chain_inst + " %ptr_ty %var " + (original.size() ? " " : "") + + original + "\n"; +} + +// Access chain into: +// Vector +// Vector sizes 2, 3, 4 +// Matrix +// Matrix columns 2, 4 +// Component is vector 2, 4 +// Array +// Struct +// TODO(dneto): RuntimeArray + +TEST_F(GraphicsRobustAccessTest, ACVectorLeastInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"( + %uvec2 = OpTypeVector %uint 2 + %var_ty = OpTypePointer Function %uvec2 + %ptr_ty = OpTypePointer Function %uint + %uint_0 = OpConstant %uint 0 + )" + << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_0", "%uint_0") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorMostInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"( + %v4uint = OpTypeVector %uint 4 + %var_ty = OpTypePointer Function %v4uint + %ptr_ty = OpTypePointer Function %uint + %uint_3 = OpConstant %uint 3 + )" + << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_3", "%uint_3") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorExcessConstantClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"( + %v4uint = OpTypeVector %uint 4 + %var_ty = OpTypePointer Function %v4uint + %ptr_ty = OpTypePointer Function %uint + %uint_4 = OpConstant %uint 4 + )" + << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_4", "%uint_3") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorNegativeConstantClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"( + %v4uint = OpTypeVector %uint 4 + %var_ty = OpTypePointer Function %v4uint + %ptr_ty = OpTypePointer Function %uint + %int_n1 = OpConstant %int -1 + )" + << MainPrefix() << R"( + ; CHECK: %int_0 = OpConstant %int 0 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%int_n1", "%int_0") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +// Like the previous test, but ensures the pass knows how to modify an index +// which does not come first in the access chain. +TEST_F(GraphicsRobustAccessTest, ACVectorInArrayNegativeConstantClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"( + %v4uint = OpTypeVector %uint 4 + %uint_1 = OpConstant %uint 1 + %uint_2 = OpConstant %uint 2 + %arr = OpTypeArray %v4uint %uint_2 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %uint + %int_n1 = OpConstant %int -1 + )" + << MainPrefix() << R"( + ; CHECK: %int_0 = OpConstant %int 0 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%uint_1 %int_n1", "%uint_1 %int_0") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorGeneralClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() << R"( + %v4uint = OpTypeVector %uint 4 + %var_ty = OpTypePointer Function %v4uint + %ptr_ty = OpTypePointer Function %uint + %i = OpUndef %int)" + << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_3 = OpConstant %int 3 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_3 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorGeneralShortClamped) { + // Show that signed 16 bit integers are clamped as well. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() << + R"( + %v4short = OpTypeVector %short 4 + %var_ty = OpTypePointer Function %v4short + %ptr_ty = OpTypePointer Function %short + %i = OpUndef %short)" + << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK-DAG: %short_0 = OpConstant %short 0 + ; CHECK-DAG: %short_3 = OpConstant %short 3 + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %short %[[GLSLSTD450]] UClamp %i %short_0 %short_3 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorGeneralUShortClamped) { + // Show that unsigned 16 bit integers are clamped as well. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() << + R"( + %v4ushort = OpTypeVector %ushort 4 + %var_ty = OpTypePointer Function %v4ushort + %ptr_ty = OpTypePointer Function %ushort + %i = OpUndef %ushort)" + << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK-DAG: %ushort_0 = OpConstant %ushort 0 + ; CHECK-DAG: %ushort_3 = OpConstant %ushort 3 + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %ushort %[[GLSLSTD450]] UClamp %i %ushort_0 %ushort_3 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorGeneralLongClamped) { + // Show that signed 64 bit integers are clamped as well. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() << + R"( + %v4long = OpTypeVector %long 4 + %var_ty = OpTypePointer Function %v4long + %ptr_ty = OpTypePointer Function %long + %i = OpUndef %long)" + << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK-DAG: %long_0 = OpConstant %long 0 + ; CHECK-DAG: %long_3 = OpConstant %long 3 + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %long_3 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACVectorGeneralULongClamped) { + // Show that unsigned 64 bit integers are clamped as well. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() << + R"( + %v4ulong = OpTypeVector %ulong 4 + %var_ty = OpTypePointer Function %v4ulong + %ptr_ty = OpTypePointer Function %ulong + %i = OpUndef %ulong)" + << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0 + ; CHECK-DAG: %ulong_3 = OpConstant %ulong 3 + ; CHECK-NOT: = OpTypeInt 32 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %ulong_3 + %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACMatrixLeastInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %v2float = OpTypeVector %float 2 + %mat4v2float = OpTypeMatrix %v2float 4 + %var_ty = OpTypePointer Function %mat4v2float + %ptr_ty = OpTypePointer Function %float + %uint_0 = OpConstant %uint 0 + %uint_1 = OpConstant %uint 1 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%uint_0 %uint_1", "%uint_0 %uint_1") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACMatrixMostInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %v2float = OpTypeVector %float 2 + %mat4v2float = OpTypeMatrix %v2float 4 + %var_ty = OpTypePointer Function %mat4v2float + %ptr_ty = OpTypePointer Function %float + %uint_1 = OpConstant %uint 1 + %uint_3 = OpConstant %uint 3 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%uint_3 %uint_1", "%uint_3 %uint_1") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACMatrixExcessConstantClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %v2float = OpTypeVector %float 2 + %mat4v2float = OpTypeMatrix %v2float 4 + %var_ty = OpTypePointer Function %mat4v2float + %ptr_ty = OpTypePointer Function %float + %uint_1 = OpConstant %uint 1 + %uint_4 = OpConstant %uint 4 + )" << MainPrefix() << R"( + ; CHECK: %uint_3 = OpConstant %uint 3 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%uint_4 %uint_1", "%uint_3 %uint_1") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACMatrixNegativeConstantClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %v2float = OpTypeVector %float 2 + %mat4v2float = OpTypeMatrix %v2float 4 + %var_ty = OpTypePointer Function %mat4v2float + %ptr_ty = OpTypePointer Function %float + %uint_1 = OpConstant %uint 1 + %int_n1 = OpConstant %int -1 + )" << MainPrefix() << R"( + ; CHECK: %int_0 = OpConstant %int 0 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%int_n1 %uint_1", "%int_0 %uint_1") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACMatrixGeneralClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %v2float = OpTypeVector %float 2 + %mat4v2float = OpTypeMatrix %v2float 4 + %var_ty = OpTypePointer Function %mat4v2float + %ptr_ty = OpTypePointer Function %float + %uint_1 = OpConstant %uint 1 + %i = OpUndef %int + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_3 = OpConstant %int 3 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_3 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i %uint_1", "%[[clamp]] %uint_1") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayLeastInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %uint_200 = OpConstant %uint 200 + %arr = OpTypeArray %float %uint_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %int_0 = OpConstant %int 0 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%int_0", "%int_0") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayMostInboundConstantUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %uint_200 = OpConstant %uint 200 + %arr = OpTypeArray %float %uint_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %int_199 = OpConstant %int 199 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%int_199", "%int_199") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %uint_200 = OpConstant %uint 200 + %arr = OpTypeArray %float %uint_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %int + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_199 = OpConstant %int 199 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_199 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralShortIndexUIntBoundsClamped) { + // Index is signed short, array bounds overflows the index type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesShort() << TypesFloat() << R"( + %uint_70000 = OpConstant %uint 70000 ; overflows 16bits + %arr = OpTypeArray %float %uint_70000 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %short + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK-DAG: %uint_69999 = OpConstant %uint 69999 + ; CHECK: OpLabel + ; CHECK: %[[i_ext:\w+]] = OpSConvert %uint %i + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %uint_69999 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUShortIndexIntBoundsClamped) { + // Index is unsigned short, array bounds overflows the index type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesShort() << TypesFloat() << R"( + %int_70000 = OpConstant %int 70000 ; overflows 16bits + %arr = OpTypeArray %float %int_70000 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %ushort + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK-DAG: %uint_69999 = OpConstant %uint 69999 + ; CHECK: OpLabel + ; CHECK: %[[i_ext:\w+]] = OpUConvert %uint %i + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %uint_69999 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUIntIndexShortBoundsClamped) { + // Signed int index i is wider than the array bounds type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesShort() << TypesFloat() << R"( + %short_200 = OpConstant %short 200 + %arr = OpTypeArray %float %short_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %uint + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK-DAG: %uint_199 = OpConstant %uint 199 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %i %uint_0 %uint_199 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralIntIndexUShortBoundsClamped) { + // Unsigned int index i is wider than the array bounds type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesShort() << TypesFloat() << R"( + %ushort_200 = OpConstant %ushort 200 + %arr = OpTypeArray %float %ushort_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %int + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_199 = OpConstant %int 199 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_199 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralLongIndexUIntBoundsClamped) { + // Signed long index i is wider than the array bounds type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesLong() << TypesFloat() << R"( + %uint_200 = OpConstant %uint 200 + %arr = OpTypeArray %float %uint_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %long + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %long_0 = OpConstant %long 0 + ; CHECK-DAG: %long_199 = OpConstant %long 199 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %long_199 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayGeneralULongIndexIntBoundsClamped) { + // Unsigned long index i is wider than the array bounds type. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64\n" + << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() + << TypesLong() << TypesFloat() << R"( + %int_200 = OpConstant %int 200 + %arr = OpTypeArray %float %int_200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %ulong + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0 + ; CHECK-DAG: %ulong_199 = OpConstant %ulong 199 + ; CHECK: OpLabel + ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %ulong_199 + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArraySpecIdSizedAlwaysClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"spec200"}) << R"( + OpDecorate %spec200 SpecId 0 )" << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %spec200 = OpSpecConstant %int 200 + %arr = OpTypeArray %float %spec200 + %var_ty = OpTypePointer Function %arr + %ptr_ty = OpTypePointer Function %float + %uint_5 = OpConstant %uint 5 + )" << MainPrefix() << R"( + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK-DAG: %uint_1 = OpConstant %uint 1 + ; CHECK: OpLabel + ; CHECK: %[[max:\w+]] = OpISub %uint %spec200 %uint_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %uint_5 %uint_0 %[[max]] + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%uint_5", "%[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructLeastUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + %int_0 = OpConstant %int 0 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%int_0", "%int_0") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructMostUntouched) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + %int_2 = OpConstant %int 2 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function)" + << ACCheck(ac, "%int_2", "%int_2") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructSpecConstantFail) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"struct", "spec200"}) + << "OpDecorate %spec200 SpecId 0\n" + << + + TypesVoid() << TypesInt() << TypesFloat() << R"( + %spec200 = OpSpecConstant %int 200 + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function + ; CHECK: Member index into struct is not a constant integer + ; CHECK-SAME: %spec200 = OpSpecConstant %int 200 + )" + << ACCheckFail(ac, "%spec200", "%spec200") << MainSuffix(); + SinglePassRunAndFail(shaders.str()); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructFloatConstantFail) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"struct"}) << + + TypesVoid() << TypesInt() << TypesFloat() << R"( + %float_2 = OpConstant %float 2 + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function + ; CHECK: Member index into struct is not a constant integer + ; CHECK-SAME: %float_2 = OpConstant %float 2 + )" + << ACCheckFail(ac, "%float_2", "%float_2") << MainSuffix(); + SinglePassRunAndFail(shaders.str()); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructNonConstantFail) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"struct", "i"}) << + + TypesVoid() << TypesInt() << TypesFloat() << R"( + %float_2 = OpConstant %float 2 + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + %i = OpUndef %int + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function + ; CHECK: Member index into struct is not a constant integer + ; CHECK-SAME: %i = OpUndef %int + )" + << ACCheckFail(ac, "%i", "%i") << MainSuffix(); + SinglePassRunAndFail(shaders.str()); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructExcessFail) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + %i = OpConstant %int 4 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function + ; CHECK: Member index 4 is out of bounds for struct type: + ; CHECK-SAME: %struct = OpTypeStruct %float %float %float + )" + << ACCheckFail(ac, "%i", "%i") << MainSuffix(); + SinglePassRunAndFail(shaders.str()); + } +} + +TEST_F(GraphicsRobustAccessTest, ACStructNegativeFail) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt() + << TypesFloat() << R"( + %struct = OpTypeStruct %float %float %float + %var_ty = OpTypePointer Function %struct + %ptr_ty = OpTypePointer Function %float + %i = OpConstant %int -1 + )" << MainPrefix() << R"( + %var = OpVariable %var_ty Function + ; CHECK: Member index -1 is out of bounds for struct type: + ; CHECK-SAME: %struct = OpTypeStruct %float %float %float + )" + << ACCheckFail(ac, "%i", "%i") << MainSuffix(); + SinglePassRunAndFail(shaders.str()); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayLeastInboundClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC() << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " + << DecoSSBO() << TypesVoid() << TypesInt() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %uint %uint %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_0 = OpConstant %int 0 + %int_2 = OpConstant %int 2 + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK: %int_1 = OpConstant %int 1 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %int_0 %int_0 %[[max]] + )" + << MainPrefix() << ACCheck(ac, "%int_2 %int_0", "%int_2 %[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralShortIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesShort() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %short %short %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %short_2 = OpConstant %short 2 + %i = OpUndef %short + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK: %uint = OpTypeInt 32 0 + ; CHECK-DAG: %uint_1 = OpConstant %uint 1 + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1 + ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %[[max]] + )" + << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUShortIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int16\n" + << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesShort() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %short %short %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %short_2 = OpConstant %short 2 + %i = OpUndef %ushort + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK: %uint = OpTypeInt 32 0 + ; CHECK-DAG: %uint_1 = OpConstant %uint 1 + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1 + ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %[[max]] + )" + << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralIntIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %int %int %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_2 = OpConstant %int 2 + %i = OpUndef %int + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_1 = OpConstant %int 1 + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]] + )" << MainPrefix() + << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUIntIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %int %int %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_2 = OpConstant %int 2 + %i = OpUndef %uint + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %uint_1 = OpConstant %uint 1 + ; CHECK-DAG: %uint_0 = OpConstant %uint 0 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %i %uint_0 %[[max]] + )" << MainPrefix() + << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralLongIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64" << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesInt() << TypesLong() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %int %int %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_2 = OpConstant %int 2 + %i = OpUndef %long + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %long_0 = OpConstant %long 0 + ; CHECK-DAG: %long_1 = OpConstant %long 1 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]] + ; CHECK: %[[max:\w+]] = OpISub %long %[[arrlen_ext]] %long_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %[[max]] + )" + << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralULongIndexClamped) { + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << "OpCapability Int64" << ShaderPreambleAC({"i"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO() + << TypesVoid() << TypesInt() << TypesLong() << TypesFloat() << R"( + %rtarr = OpTypeRuntimeArray %float + %ssbo_s = OpTypeStruct %int %int %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_2 = OpConstant %int 2 + %i = OpUndef %ulong + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0 + ; CHECK-DAG: %ulong_1 = OpConstant %ulong 1 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]] + ; CHECK: %[[max:\w+]] = OpISub %ulong %[[arrlen_ext]] %ulong_1 + ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %[[max]] + )" + << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACRTArrayStructVectorElem) { + // The point of this test is that the access chain can have indices past the + // index into the runtime array. For good measure, the index into the final + // struct is out of bounds. We have to clamp that index too. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i", "j"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n" + << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n" + << "OpMemberDecorate %rtelem 1 Offset 16\n" + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %v4float = OpTypeVector %float 4 + %rtelem = OpTypeStruct %v4float %v4float + %rtarr = OpTypeRuntimeArray %rtelem + %ssbo_s = OpTypeStruct %int %int %rtarr + %var_ty = OpTypePointer Uniform %ssbo_s + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %i = OpUndef %int + %j = OpUndef %int + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_3 = OpConstant %int 3 + ; CHECK: OpLabel + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]] + ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %int_3 + )" << MainPrefix() + << ACCheck(ac, "%int_2 %i %int_1 %j", + "%int_2 %[[clamp_i]] %int_1 %[[clamp_j]]") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACArrayRTArrayStructVectorElem) { + // Now add an additional level of arrays around the Block-decorated struct. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i", "ssbo_s"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n" + << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n" + << "OpMemberDecorate %rtelem 1 Offset 16\n" + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %v4float = OpTypeVector %float 4 + %rtelem = OpTypeStruct %v4float %v4float + %rtarr = OpTypeRuntimeArray %rtelem + %ssbo_s = OpTypeStruct %int %int %rtarr + %arr_size = OpConstant %int 10 + %arr_ssbo = OpTypeArray %ssbo_s %arr_size + %var_ty = OpTypePointer Uniform %arr_ssbo + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %int_17 = OpConstant %int 17 + %i = OpUndef %int + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %[[ssbo_p:\w+]] = OpTypePointer Uniform %ssbo_s + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_9 = OpConstant %int 9 + ; CHECK: OpLabel + ; This access chain is manufatured only so we can compute the array length. + ; Note that the %int_9 is already clamped + ; CHECK: %[[ssbo_base:\w+]] = )" << ac + << R"( %[[ssbo_p]] %var %int_9 + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %[[ssbo_base]] 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]] + )" << MainPrefix() + << ACCheck(ac, "%int_17 %int_2 %i %int_1 %int_2", + "%int_9 %int_2 %[[clamp_i]] %int_1 %int_2") + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, ACSplitACArrayRTArrayStructVectorElem) { + // Split the address calculation across two access chains. Force + // the transform to walk up the access chains to find the base variable. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i", "j", "k", "ssbo_s", "ssbo_pty", + "rtarr_pty", "ac_ssbo", "ac_rtarr"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n" + << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n" + << "OpMemberDecorate %rtelem 1 Offset 16\n" + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %v4float = OpTypeVector %float 4 + %rtelem = OpTypeStruct %v4float %v4float + %rtarr = OpTypeRuntimeArray %rtelem + %ssbo_s = OpTypeStruct %int %int %rtarr + %arr_size = OpConstant %int 10 + %arr_ssbo = OpTypeArray %ssbo_s %arr_size + %var_ty = OpTypePointer Uniform %arr_ssbo + %ssbo_pty = OpTypePointer Uniform %ssbo_s + %rtarr_pty = OpTypePointer Uniform %rtarr + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %i = OpUndef %int + %j = OpUndef %int + %k = OpUndef %int + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_9 = OpConstant %int 9 + ; CHECK-DAG: %int_3 = OpConstant %int 3 + ; CHECK: OpLabel + ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_9 + ; CHECK: %ac_ssbo = )" << ac + << R"( %ssbo_pty %var %[[clamp_i]] + ; CHECK: %ac_rtarr = )" + << ac << R"( %rtarr_pty %ac_ssbo %int_2 + + ; This is the interesting bit. This array length is needed for an OpAccessChain + ; computing %ac, but the algorithm had to track back through %ac_rtarr's + ; definition to find the base pointer %ac_ssbo. + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %[[max]] + ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %k %int_0 %int_3 + ; CHECK: %ac = )" << ac + << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]] + ; CHECK-NOT: AccessChain + )" << MainPrefix() + << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n" + << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n" + << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n" + + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +TEST_F(GraphicsRobustAccessTest, + ACSplitACArrayRTArrayStructVectorElemAcrossBasicBlocks) { + // Split the address calculation across two access chains. Force + // the transform to walk up the access chains to find the base variable. + // This time, put the different access chains in different basic blocks. + // This sanity checks that we keep the instruction-to-block mapping + // consistent. + for (auto* ac : AccessChains()) { + std::ostringstream shaders; + shaders << ShaderPreambleAC({"i", "j", "k", "bb1", "bb2", "ssbo_s", + "ssbo_pty", "rtarr_pty", "ac_ssbo", + "ac_rtarr"}) + << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n" + << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n" + << "OpMemberDecorate %rtelem 1 Offset 16\n" + << TypesVoid() << TypesInt() << TypesFloat() << R"( + %v4float = OpTypeVector %float 4 + %rtelem = OpTypeStruct %v4float %v4float + %rtarr = OpTypeRuntimeArray %rtelem + %ssbo_s = OpTypeStruct %int %int %rtarr + %arr_size = OpConstant %int 10 + %arr_ssbo = OpTypeArray %ssbo_s %arr_size + %var_ty = OpTypePointer Uniform %arr_ssbo + %ssbo_pty = OpTypePointer Uniform %ssbo_s + %rtarr_pty = OpTypePointer Uniform %rtarr + %ptr_ty = OpTypePointer Uniform %float + %var = OpVariable %var_ty Uniform + %int_1 = OpConstant %int 1 + %int_2 = OpConstant %int 2 + %i = OpUndef %int + %j = OpUndef %int + %k = OpUndef %int + ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450" + ; CHECK-DAG: %int_0 = OpConstant %int 0 + ; CHECK-DAG: %int_9 = OpConstant %int 9 + ; CHECK-DAG: %int_3 = OpConstant %int 3 + ; CHECK: OpLabel + ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_9 + ; CHECK: %ac_ssbo = )" << ac + << R"( %ssbo_pty %var %[[clamp_i]] + ; CHECK: %bb1 = OpLabel + ; CHECK: %ac_rtarr = )" + << ac << R"( %rtarr_pty %ac_ssbo %int_2 + ; CHECK: %bb2 = OpLabel + + ; This is the interesting bit. This array length is needed for an OpAccessChain + ; computing %ac, but the algorithm had to track back through %ac_rtarr's + ; definition to find the base pointer %ac_ssbo. + ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2 + ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1 + ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %[[max]] + ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %k %int_0 %int_3 + ; CHECK: %ac = )" << ac + << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]] + ; CHECK-NOT: AccessChain + )" << MainPrefix() + << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n" + << "OpBranch %bb1\n%bb1 = OpLabel\n" + << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n" + << "OpBranch %bb2\n%bb2 = OpLabel\n" + << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n" + + << MainSuffix(); + SinglePassRunAndMatch(shaders.str(), true); + } +} + +// TODO(dneto): Test access chain index wider than 64 bits? +// TODO(dneto): Test struct access chain index wider than 64 bits? +// TODO(dneto): OpImageTexelPointer +// - all Dim types: 1D 2D Cube 3D Rect Buffer +// - all Dim types that can be arrayed: 1D 2D 3D +// - sample index: set to 0 if not multisampled +// - Dim (2D, Cube Rect} with multisampling +// -1 0 max excess +// TODO(dneto): Test OpImageTexelPointer with coordinate component index other +// than 32 bits. + +} // namespace diff --git a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp index 6fe27c64a..6e1adaa60 100644 --- a/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inst_bindless_check_test.cpp @@ -5706,6 +5706,282 @@ OpFunctionEnd true, 7u, 23u, false, false, 2u); } +TEST_F(InstBindlessTest, InstrumentTeseSimpleV2) { + // This test verifies that the pass will correctly instrument tessellation + // evaluation shader doing bindless buffer load. + // + // clang-format off + // + // #version 450 + // #extension GL_EXT_nonuniform_qualifier : enable + // + // layout(std140, set = 0, binding = 0) uniform ufoo { uint index; } uniform_index_buffer; + // + // layout(set = 0, binding = 1) buffer bfoo { vec4 val; } adds[11]; + // + // layout(triangles, equal_spacing, cw) in; + // + // void main() { + // gl_Position = adds[uniform_index_buffer.index].val; + // } + // + // clang-format on + + const std::string defs_before = + R"(OpCapability Tessellation +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TessellationEvaluation %main "main" %_ +OpExecutionMode %main Triangles +OpExecutionMode %main SpacingEqual +OpExecutionMode %main VertexOrderCw +OpSource GLSL 450 +OpSourceExtension "GL_EXT_nonuniform_qualifier" +OpName %main "main" +OpName %gl_PerVertex "gl_PerVertex" +OpMemberName %gl_PerVertex 0 "gl_Position" +OpMemberName %gl_PerVertex 1 "gl_PointSize" +OpMemberName %gl_PerVertex 2 "gl_ClipDistance" +OpMemberName %gl_PerVertex 3 "gl_CullDistance" +OpName %_ "" +OpName %bfoo "bfoo" +OpMemberName %bfoo 0 "val" +OpName %adds "adds" +OpName %ufoo "ufoo" +OpMemberName %ufoo 0 "index" +OpName %uniform_index_buffer "uniform_index_buffer" +OpMemberDecorate %gl_PerVertex 0 BuiltIn Position +OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize +OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance +OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance +OpDecorate %gl_PerVertex Block +OpMemberDecorate %bfoo 0 Offset 0 +OpDecorate %bfoo Block +OpDecorate %adds DescriptorSet 0 +OpDecorate %adds Binding 1 +OpMemberDecorate %ufoo 0 Offset 0 +OpDecorate %ufoo Block +OpDecorate %uniform_index_buffer DescriptorSet 0 +OpDecorate %uniform_index_buffer Binding 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%_arr_float_uint_1 = OpTypeArray %float %uint_1 +%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1 +%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex +%_ = OpVariable %_ptr_Output_gl_PerVertex Output +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%bfoo = OpTypeStruct %v4float +%uint_11 = OpConstant %uint 11 +%_arr_bfoo_uint_11 = OpTypeArray %bfoo %uint_11 +%_ptr_StorageBuffer__arr_bfoo_uint_11 = OpTypePointer StorageBuffer %_arr_bfoo_uint_11 +%adds = OpVariable %_ptr_StorageBuffer__arr_bfoo_uint_11 StorageBuffer +%ufoo = OpTypeStruct %uint +%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo +%uniform_index_buffer = OpVariable %_ptr_Uniform_ufoo Uniform +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +)"; + + const std::string defs_after = + R"(OpCapability Tessellation +OpExtension "SPV_KHR_storage_buffer_storage_class" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TessellationEvaluation %main "main" %_ %gl_PrimitiveID %gl_TessCoord +OpExecutionMode %main Triangles +OpExecutionMode %main SpacingEqual +OpExecutionMode %main VertexOrderCw +OpSource GLSL 450 +OpSourceExtension "GL_EXT_nonuniform_qualifier" +OpName %main "main" +OpName %gl_PerVertex "gl_PerVertex" +OpMemberName %gl_PerVertex 0 "gl_Position" +OpMemberName %gl_PerVertex 1 "gl_PointSize" +OpMemberName %gl_PerVertex 2 "gl_ClipDistance" +OpMemberName %gl_PerVertex 3 "gl_CullDistance" +OpName %_ "" +OpName %bfoo "bfoo" +OpMemberName %bfoo 0 "val" +OpName %adds "adds" +OpName %ufoo "ufoo" +OpMemberName %ufoo 0 "index" +OpName %uniform_index_buffer "uniform_index_buffer" +OpMemberDecorate %gl_PerVertex 0 BuiltIn Position +OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize +OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance +OpMemberDecorate %gl_PerVertex 3 BuiltIn CullDistance +OpDecorate %gl_PerVertex Block +OpMemberDecorate %bfoo 0 Offset 0 +OpDecorate %bfoo Block +OpDecorate %adds DescriptorSet 0 +OpDecorate %adds Binding 1 +OpMemberDecorate %ufoo 0 Offset 0 +OpDecorate %ufoo Block +OpDecorate %uniform_index_buffer DescriptorSet 0 +OpDecorate %uniform_index_buffer Binding 0 +OpDecorate %_runtimearr_uint ArrayStride 4 +OpDecorate %_struct_47 Block +OpMemberDecorate %_struct_47 0 Offset 0 +OpMemberDecorate %_struct_47 1 Offset 4 +OpDecorate %49 DescriptorSet 7 +OpDecorate %49 Binding 0 +OpDecorate %gl_PrimitiveID BuiltIn PrimitiveId +OpDecorate %gl_TessCoord BuiltIn TessCoord +%void = OpTypeVoid +%10 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%uint = OpTypeInt 32 0 +%uint_1 = OpConstant %uint 1 +%_arr_float_uint_1 = OpTypeArray %float %uint_1 +%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1 %_arr_float_uint_1 +%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex +%_ = OpVariable %_ptr_Output_gl_PerVertex Output +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%bfoo = OpTypeStruct %v4float +%uint_11 = OpConstant %uint 11 +%_arr_bfoo_uint_11 = OpTypeArray %bfoo %uint_11 +%_ptr_StorageBuffer__arr_bfoo_uint_11 = OpTypePointer StorageBuffer %_arr_bfoo_uint_11 +%adds = OpVariable %_ptr_StorageBuffer__arr_bfoo_uint_11 StorageBuffer +%ufoo = OpTypeStruct %uint +%_ptr_Uniform_ufoo = OpTypePointer Uniform %ufoo +%uniform_index_buffer = OpVariable %_ptr_Uniform_ufoo Uniform +%_ptr_Uniform_uint = OpTypePointer Uniform %uint +%_ptr_StorageBuffer_v4float = OpTypePointer StorageBuffer %v4float +%_ptr_Output_v4float = OpTypePointer Output %v4float +%uint_0 = OpConstant %uint 0 +%bool = OpTypeBool +%40 = OpTypeFunction %void %uint %uint %uint %uint +%_runtimearr_uint = OpTypeRuntimeArray %uint +%_struct_47 = OpTypeStruct %uint %_runtimearr_uint +%_ptr_StorageBuffer__struct_47 = OpTypePointer StorageBuffer %_struct_47 +%49 = OpVariable %_ptr_StorageBuffer__struct_47 StorageBuffer +%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint +%uint_10 = OpConstant %uint 10 +%uint_4 = OpConstant %uint 4 +%uint_23 = OpConstant %uint 23 +%uint_2 = OpConstant %uint 2 +%uint_3 = OpConstant %uint 3 +%_ptr_Input_uint = OpTypePointer Input %uint +%gl_PrimitiveID = OpVariable %_ptr_Input_uint Input +%v3float = OpTypeVector %float 3 +%_ptr_Input_v3float = OpTypePointer Input %v3float +%gl_TessCoord = OpVariable %_ptr_Input_v3float Input +%v3uint = OpTypeVector %uint 3 +%uint_5 = OpConstant %uint 5 +%uint_6 = OpConstant %uint 6 +%uint_7 = OpConstant %uint 7 +%uint_8 = OpConstant %uint 8 +%uint_9 = OpConstant %uint 9 +%uint_63 = OpConstant %uint 63 +%101 = OpConstantNull %v4float +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %3 +%5 = OpLabel +%25 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0 +%26 = OpLoad %uint %25 +%28 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %26 %int_0 +%29 = OpLoad %v4float %28 +%31 = OpAccessChain %_ptr_Output_v4float %_ %int_0 +OpStore %31 %29 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %10 +%26 = OpLabel +%27 = OpAccessChain %_ptr_Uniform_uint %uniform_index_buffer %int_0 +%28 = OpLoad %uint %27 +%29 = OpAccessChain %_ptr_StorageBuffer_v4float %adds %28 %int_0 +%34 = OpULessThan %bool %28 %uint_11 +OpSelectionMerge %35 None +OpBranchConditional %34 %36 %37 +%36 = OpLabel +%38 = OpLoad %v4float %29 +OpBranch %35 +%37 = OpLabel +%100 = OpFunctionCall %void %39 %uint_63 %uint_0 %28 %uint_11 +OpBranch %35 +%35 = OpLabel +%102 = OpPhi %v4float %38 %36 %101 %37 +%31 = OpAccessChain %_ptr_Output_v4float %_ %int_0 +OpStore %31 %102 +OpReturn +OpFunctionEnd +)"; + + const std::string output_func = + R"(%39 = OpFunction %void None %40 +%41 = OpFunctionParameter %uint +%42 = OpFunctionParameter %uint +%43 = OpFunctionParameter %uint +%44 = OpFunctionParameter %uint +%45 = OpLabel +%51 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_0 +%54 = OpAtomicIAdd %uint %51 %uint_4 %uint_0 %uint_10 +%55 = OpIAdd %uint %54 %uint_10 +%56 = OpArrayLength %uint %49 1 +%57 = OpULessThanEqual %bool %55 %56 +OpSelectionMerge %58 None +OpBranchConditional %57 %59 %58 +%59 = OpLabel +%60 = OpIAdd %uint %54 %uint_0 +%61 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %60 +OpStore %61 %uint_10 +%63 = OpIAdd %uint %54 %uint_1 +%64 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %63 +OpStore %64 %uint_23 +%66 = OpIAdd %uint %54 %uint_2 +%67 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %66 +OpStore %67 %41 +%69 = OpIAdd %uint %54 %uint_3 +%70 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %69 +OpStore %70 %uint_2 +%73 = OpLoad %uint %gl_PrimitiveID +%74 = OpIAdd %uint %54 %uint_4 +%75 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %74 +OpStore %75 %73 +%79 = OpLoad %v3float %gl_TessCoord +%81 = OpBitcast %v3uint %79 +%82 = OpCompositeExtract %uint %81 0 +%83 = OpCompositeExtract %uint %81 1 +%85 = OpIAdd %uint %54 %uint_5 +%86 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %85 +OpStore %86 %82 +%88 = OpIAdd %uint %54 %uint_6 +%89 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %88 +OpStore %89 %83 +%91 = OpIAdd %uint %54 %uint_7 +%92 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %91 +OpStore %92 %42 +%94 = OpIAdd %uint %54 %uint_8 +%95 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %94 +OpStore %95 %43 +%97 = OpIAdd %uint %54 %uint_9 +%98 = OpAccessChain %_ptr_StorageBuffer_uint %49 %uint_1 %97 +OpStore %98 %44 +OpBranch %58 +%58 = OpLabel +OpReturn +OpFunctionEnd +)"; + + // SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck( + defs_before + func_before, defs_after + func_after + output_func, true, + true, 7u, 23u, false, false, 2u); +} + TEST_F(InstBindlessTest, MultipleDebugFunctionsV2) { // Same source as Simple, but compiled -g and not optimized, especially not // inlined. The OpSource has had the source extracted for the sake of brevity. diff --git a/3rdparty/spirv-tools/test/opt/pass_fixture.h b/3rdparty/spirv-tools/test/opt/pass_fixture.h index b7a07426b..a4d749f78 100644 --- a/3rdparty/spirv-tools/test/opt/pass_fixture.h +++ b/3rdparty/spirv-tools/test/opt/pass_fixture.h @@ -188,6 +188,32 @@ class PassTest : public TestT { << disassembly; } + // Runs a single pass of class |PassT| on the binary assembled from the + // |original| assembly. Check for failure and expect an Effcee matcher + // to pass when run on the diagnostic messages. This does *not* involve + // pass manager. Callers are suggested to use SCOPED_TRACE() for better + // messages. + template + void SinglePassRunAndFail(const std::string& original, Args&&... args) { + context_ = + std::move(BuildModule(env_, consumer_, original, assemble_options_)); + EXPECT_NE(nullptr, context()) << "Assembling failed for shader:\n" + << original << std::endl; + std::ostringstream errs; + auto error_consumer = [&errs](spv_message_level_t, const char*, + const spv_position_t&, const char* message) { + errs << message << std::endl; + }; + auto pass = MakeUnique(std::forward(args)...); + pass->SetMessageConsumer(error_consumer); + const auto status = pass->Run(context()); + EXPECT_EQ(Pass::Status::Failure, status); + auto match_result = effcee::Match(errs.str(), original); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking messages:\n" + << errs.str(); + } + // Adds a pass to be run. template void AddPass(Args&&... args) { diff --git a/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp b/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp index baf7820e8..a30568063 100644 --- a/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp +++ b/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp @@ -1779,6 +1779,7 @@ TEST_F(MergeReturnPassTest, MergeToMergeBranch) { SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); SinglePassRunAndMatch(text, true); } + TEST_F(MergeReturnPassTest, PhiInSecondMerge) { // Add and use a phi in the second merge block from the return. const std::string text = @@ -1831,6 +1832,50 @@ TEST_F(MergeReturnPassTest, PhiInSecondMerge) { SinglePassRunAndMatch(text, true); } +TEST_F(MergeReturnPassTest, UnreachableMergeAndContinue) { + // Make sure that the pass can handle a single block that is both a merge and + // a continue. + const std::string text = + R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %void = OpTypeVoid + %4 = OpTypeFunction %void + %bool = OpTypeBool + %true = OpConstantTrue %bool + %2 = OpFunction %void None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpLoopMerge %9 %10 None + OpBranch %11 + %11 = OpLabel + OpSelectionMerge %10 None + OpBranchConditional %true %12 %13 + %12 = OpLabel + OpReturn + %13 = OpLabel + OpReturn + %10 = OpLabel + OpBranch %8 + %9 = OpLabel + OpUnreachable + OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + auto result = SinglePassRunAndDisassemble(text, true, true); + + // Not looking for any particular output. Other tests do that. + // Just want to make sure the check for unreachable blocks does not emit an + // error. + EXPECT_EQ(Pass::Status::SuccessWithChange, std::get<1>(result)); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp index 6c182d5e6..00f8e17b6 100644 --- a/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp +++ b/3rdparty/spirv-tools/test/opt/scalar_replacement_test.cpp @@ -1657,6 +1657,153 @@ OpFunctionEnd EXPECT_EQ(Pass::Status::Failure, std::get<1>(result)); } +// Test that replacements for OpAccessChain do not go out of bounds. +// https://github.com/KhronosGroup/SPIRV-Tools/issues/2609. +TEST_F(ScalarReplacementTest, OutOfBoundOpAccessChain) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %main "main" %_GLF_color + OpExecutionMode %main OriginUpperLeft + OpSource ESSL 310 + OpName %main "main" + OpName %a "a" + OpName %_GLF_color "_GLF_color" + OpDecorate %_GLF_color Location 0 + %void = OpTypeVoid + %3 = OpTypeFunction %void + %int = OpTypeInt 32 1 +%_ptr_Function_int = OpTypePointer Function %int + %int_1 = OpConstant %int 1 + %float = OpTypeFloat 32 + %uint = OpTypeInt 32 0 + %uint_1 = OpConstant %uint 1 +%_arr_float_uint_1 = OpTypeArray %float %uint_1 +%_ptr_Function__arr_float_uint_1 = OpTypePointer Function %_arr_float_uint_1 +%_ptr_Function_float = OpTypePointer Function %float +%_ptr_Output_float = OpTypePointer Output %float + %_GLF_color = OpVariable %_ptr_Output_float Output + %main = OpFunction %void None %3 + %5 = OpLabel + %a = OpVariable %_ptr_Function__arr_float_uint_1 Function + %21 = OpAccessChain %_ptr_Function_float %a %int_1 + %22 = OpLoad %float %21 + OpStore %_GLF_color %22 + OpReturn + OpFunctionEnd + )"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + + auto result = + SinglePassRunAndDisassemble(text, true, false); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ScalarReplacementTest, CharIndex) { + const std::string text = R"( +; CHECK: [[int:%\w+]] = OpTypeInt 32 0 +; CHECK: [[ptr:%\w+]] = OpTypePointer Function [[int]] +; CHECK: OpVariable [[ptr]] Function +OpCapability Shader +OpCapability Int8 +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_1024 = OpConstant %int 1024 +%char = OpTypeInt 8 0 +%char_1 = OpConstant %char 1 +%array = OpTypeArray %int %int_1024 +%ptr_func_array = OpTypePointer Function %array +%ptr_func_int = OpTypePointer Function %int +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_func_array Function +%gep = OpAccessChain %ptr_func_int %var %char_1 +OpStore %gep %int_1024 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true, 0); +} + +TEST_F(ScalarReplacementTest, OutOfBoundsOpAccessChainNegative) { + const std::string text = R"( +OpCapability Shader +OpCapability Int8 +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_1024 = OpConstant %int 1024 +%char = OpTypeInt 8 1 +%char_n1 = OpConstant %char -1 +%array = OpTypeArray %int %int_1024 +%ptr_func_array = OpTypePointer Function %array +%ptr_func_int = OpTypePointer Function %int +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +%var = OpVariable %ptr_func_array Function +%gep = OpAccessChain %ptr_func_int %var %char_n1 +OpStore %gep %int_1024 +OpReturn +OpFunctionEnd +)"; + + auto result = + SinglePassRunAndDisassemble(text, true, true, 0); + EXPECT_EQ(Pass::Status::SuccessWithoutChange, std::get<1>(result)); +} + +TEST_F(ScalarReplacementTest, RelaxedPrecisionMemberDecoration) { + const std::string text = R"( +; CHECK: OpDecorate {{%\w+}} RelaxedPrecision +; CHECK: OpDecorate [[new_var:%\w+]] RelaxedPrecision +; CHECK: [[new_var]] = OpVariable %_ptr_Function_v3float Function +; CHECK: OpLoad %v3float [[new_var]] + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "Draw2DTexCol_VS" %2 %3 + OpSource HLSL 600 + OpDecorate %2 Location 0 + OpDecorate %3 Location 1 + OpDecorate %3 RelaxedPrecision + OpMemberDecorate %_struct_4 1 RelaxedPrecision + %float = OpTypeFloat 32 + %int = OpTypeInt 32 1 + %int_1 = OpConstant %int 1 + %v3float = OpTypeVector %float 3 +%_ptr_Input_v3float = OpTypePointer Input %v3float + %void = OpTypeVoid + %11 = OpTypeFunction %void + %_struct_4 = OpTypeStruct %v3float %v3float +%_ptr_Function__struct_4 = OpTypePointer Function %_struct_4 +%_ptr_Function_v3float = OpTypePointer Function %v3float + %2 = OpVariable %_ptr_Input_v3float Input + %3 = OpVariable %_ptr_Input_v3float Input + %1 = OpFunction %void None %11 + %14 = OpLabel + %15 = OpVariable %_ptr_Function__struct_4 Function + %16 = OpLoad %v3float %2 + %17 = OpLoad %v3float %3 + %18 = OpCompositeConstruct %_struct_4 %16 %17 + OpStore %15 %18 + %19 = OpAccessChain %_ptr_Function_v3float %15 %int_1 + %20 = OpLoad %v3float %19 + OpReturn + OpFunctionEnd +)"; + + SinglePassRunAndMatch(text, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/simplification_test.cpp b/3rdparty/spirv-tools/test/opt/simplification_test.cpp index 4dbcfbe39..14204980c 100644 --- a/3rdparty/spirv-tools/test/opt/simplification_test.cpp +++ b/3rdparty/spirv-tools/test/opt/simplification_test.cpp @@ -279,6 +279,52 @@ OpFunctionEnd SinglePassRunAndCheck(before, after, false); } +TEST_F(SimplificationTest, DontMoveDecorations) { + const std::string spirv = R"( +; CHECK-NOT: RelaxedPrecision +; CHECK: [[sub:%\w+]] = OpFSub +; CHECK: OpStore {{.*}} [[sub]] +OpCapability Shader +OpMemoryModel Logical GLSL450 +OpEntryPoint GLCompute %main "main" +OpExecutionMode %main LocalSize 1 1 1 +OpDecorate %add RelaxedPrecision +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 1 Offset 4 +OpDecorate %in DescriptorSet 0 +OpDecorate %in Binding 0 +OpDecorate %out DescriptorSet 0 +OpDecorate %out Binding 1 +%void = OpTypeVoid +%float = OpTypeFloat 32 +%void_fn = OpTypeFunction %void +%block = OpTypeStruct %float %float +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%in = OpVariable %ptr_ssbo_block StorageBuffer +%out = OpVariable %ptr_ssbo_block StorageBuffer +%ptr_ssbo_float = OpTypePointer StorageBuffer %float +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%float_0 = OpConstant %float 0 +%main = OpFunction %void None %void_fn +%entry = OpLabel +%in_gep_0 = OpAccessChain %ptr_ssbo_float %in %int_0 +%in_gep_1 = OpAccessChain %ptr_ssbo_float %in %int_1 +%load_0 = OpLoad %float %in_gep_0 +%load_1 = OpLoad %float %in_gep_1 +%sub = OpFSub %float %load_0 %load_1 +%add = OpFAdd %float %float_0 %sub +%out_gep_0 = OpAccessChain %ptr_ssbo_float %out %int_0 +OpStore %out_gep_0 %add +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndMatch(spirv, true); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp index 69a48610d..61bdf64c8 100644 --- a/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp +++ b/3rdparty/spirv-tools/test/text_to_binary.annotation_test.cpp @@ -21,6 +21,7 @@ #include #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -29,7 +30,7 @@ namespace { using spvtest::EnumCase; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using spvtest::TextToBinaryTest; using ::testing::Combine; using ::testing::Eq; diff --git a/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp index f9a464586..39ba5c524 100644 --- a/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp +++ b/3rdparty/spirv-tools/test/text_to_binary.debug_test.cpp @@ -19,6 +19,7 @@ #include #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -26,7 +27,7 @@ namespace spvtools { namespace { using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using spvtest::TextToBinaryTest; using ::testing::Eq; diff --git a/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp index 84552b534..9408e9ac2 100644 --- a/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp +++ b/3rdparty/spirv-tools/test/text_to_binary.extension_test.cpp @@ -22,6 +22,7 @@ #include "gmock/gmock.h" #include "source/latest_version_glsl_std_450_header.h" #include "source/latest_version_opencl_std_header.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -30,7 +31,7 @@ namespace { using spvtest::Concatenate; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using spvtest::TextToBinaryTest; using ::testing::Combine; using ::testing::Eq; diff --git a/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp b/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp index d1b69dd5e..8ddf42196 100644 --- a/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp +++ b/3rdparty/spirv-tools/test/text_to_binary.mode_setting_test.cpp @@ -20,6 +20,7 @@ #include #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" #include "test/unit_spirv.h" @@ -28,7 +29,7 @@ namespace { using spvtest::EnumCase; using spvtest::MakeInstruction; -using spvtest::MakeVector; +using utils::MakeVector; using ::testing::Combine; using ::testing::Eq; using ::testing::TestWithParam; diff --git a/3rdparty/spirv-tools/test/unit_spirv.cpp b/3rdparty/spirv-tools/test/unit_spirv.cpp index 84ed87a51..085443948 100644 --- a/3rdparty/spirv-tools/test/unit_spirv.cpp +++ b/3rdparty/spirv-tools/test/unit_spirv.cpp @@ -15,12 +15,13 @@ #include "test/unit_spirv.h" #include "gmock/gmock.h" +#include "source/util/string_utils.h" #include "test/test_fixture.h" namespace spvtools { namespace { -using spvtest::MakeVector; +using utils::MakeVector; using ::testing::Eq; using Words = std::vector; diff --git a/3rdparty/spirv-tools/test/unit_spirv.h b/3rdparty/spirv-tools/test/unit_spirv.h index 224428884..32646620d 100644 --- a/3rdparty/spirv-tools/test/unit_spirv.h +++ b/3rdparty/spirv-tools/test/unit_spirv.h @@ -133,29 +133,6 @@ inline std::vector Concatenate( return result; } -// Encodes a string as a sequence of words, using the SPIR-V encoding. -inline std::vector MakeVector(std::string input) { - std::vector result; - uint32_t word = 0; - size_t num_bytes = input.size(); - // SPIR-V strings are null-terminated. The byte_index == num_bytes - // case is used to push the terminating null byte. - for (size_t byte_index = 0; byte_index <= num_bytes; byte_index++) { - const auto new_byte = - (byte_index < num_bytes ? uint8_t(input[byte_index]) : uint8_t(0)); - word |= (new_byte << (8 * (byte_index % sizeof(uint32_t)))); - if (3 == (byte_index % sizeof(uint32_t))) { - result.push_back(word); - word = 0; - } - } - // Emit a trailing partial word. - if ((num_bytes + 1) % sizeof(uint32_t)) { - result.push_back(word); - } - return result; -} - // A type for easily creating spv_text_t values, with an implicit conversion to // spv_text. struct AutoText { diff --git a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp index 15887eb36..57a1187b4 100644 --- a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp @@ -377,34 +377,34 @@ TEST_F(ValidateAtomics, AtomicLoadVulkanInt64) { TEST_F(ValidateAtomics, AtomicLoadWebGPUSuccess) { const std::string body = R"( %val1 = OpAtomicLoad %u32 %u32_var %queuefamily %relaxed -%val2 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); } +TEST_F(ValidateAtomics, AtomicLoadWebGPUNonQueueFamilyFailure) { + const std::string body = R"( +%val3 = OpAtomicLoad %u32 %u32_var %invocation %relaxed +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Memory Scope is limited to QueueFamilyKHR for " + "OpAtomic* operations")); +} + TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) { const std::string body = R"( %val1 = OpAtomicLoad %u32 %u32_var %queuefamily %acquire -%val2 = OpAtomicLoad %u32 %u32_var %workgroup %release )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks")); -} - -TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) { - const std::string body = R"( -%val3 = OpAtomicLoad %u32 %u32_var %invocation %sequentially_consistent -)"; - - CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks")); + HasSubstr("no bits may be set for Memory Semantics of OpAtomic* " + "instructions")); } TEST_F(ValidateAtomics, VK_KHR_shader_atomic_int64Success) { @@ -592,6 +592,17 @@ OpAtomicStore %u32_var %queuefamily %relaxed %u32_1 CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); } +TEST_F(ValidateAtomics, AtomicStoreWebGPUNonQueueFamilyFailure) { + const std::string body = R"( +OpAtomicStore %u32_var %workgroup %relaxed %u32_1 +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Memory Scope is limited to QueueFamilyKHR for " + "OpAtomic* operations")); +} TEST_F(ValidateAtomics, AtomicStoreWebGPUNonRelaxedFailure) { const std::string body = R"( @@ -601,18 +612,8 @@ OpAtomicStore %u32_var %queuefamily %release %u32_1 CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks")); -} - -TEST_F(ValidateAtomics, AtomicStoreWebGPUSequentiallyConsistent) { - const std::string body = R"( -OpAtomicStore %u32_var %queuefamily %sequentially_consistent %u32_1 -)"; - - CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec disallows, for OpAtomic*, any bit masks")); + HasSubstr("no bits may be set for Memory Semantics of OpAtomic* " + "instructions")); } TEST_F(ValidateAtomics, AtomicStoreWrongPointerType) { @@ -1919,11 +1920,9 @@ TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) { CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Invocation, and QueueFamilyKHR\n" - " %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "QueueFamilyKHR for OpAtomic* operations")); } TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) { @@ -1933,20 +1932,21 @@ TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) { CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Invocation, and QueueFamilyKHR\n" - " %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "QueueFamilyKHR for OpAtomic* operations")); } -TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) { +TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeBad) { const std::string body = R"( %val1 = OpAtomicLoad %u32 %u32_var %workgroup %relaxed )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "QueueFamilyKHR for OpAtomic* operations")); } TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) { @@ -1956,20 +1956,21 @@ TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) { CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Invocation, and QueueFamilyKHR\n" - " %34 = OpAtomicLoad %uint %29 %uint_3 %uint_0_1\n")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "QueueFamilyKHR for OpAtomic* operations")); } -TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeGood) { +TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) { const std::string body = R"( %val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "QueueFamilyKHR for OpAtomic* operations")); } TEST_F(ValidateAtomics, WebGPUQueueFamilyMemoryScopeGood) { diff --git a/3rdparty/spirv-tools/test/val/val_barriers_test.cpp b/3rdparty/spirv-tools/test/val/val_barriers_test.cpp index 221419766..18f57f80a 100644 --- a/3rdparty/spirv-tools/test/val/val_barriers_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_barriers_test.cpp @@ -82,9 +82,12 @@ OpCapability Shader %release_uniform_workgroup = OpConstant %u32 324 %acquire_and_release_uniform = OpConstant %u32 70 %acquire_release_subgroup = OpConstant %u32 136 +%acquire_release_workgroup = OpConstant %u32 264 %uniform = OpConstant %u32 64 %uniform_workgroup = OpConstant %u32 320 - +%workgroup_memory = OpConstant %u32 256 +%image_memory = OpConstant %u32 2048 +%uniform_image_memory = OpConstant %u32 2112 %main = OpFunction %void None %func %main_entry = OpLabel @@ -251,7 +254,7 @@ OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireReleaseSuccess) { const std::string body = R"( -OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup +OpControlBarrier %workgroup %workgroup %acquire_release_workgroup )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); @@ -260,25 +263,39 @@ OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup TEST_F(ValidateBarriers, OpControlBarrierWebGPURelaxedFailure) { const std::string body = R"( -OpControlBarrier %workgroup %workgroup %uniform_workgroup +OpControlBarrier %workgroup %workgroup %workgroup )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec requires AcquireRelease to set")); + HasSubstr("For WebGPU, AcquireRelease must be set for Memory " + "Semantics of OpControlBarrier")); } -TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireFailure) { +TEST_F(ValidateBarriers, OpControlBarrierWebGPUMissingWorkgroupFailure) { const std::string body = R"( -OpControlBarrier %workgroup %workgroup %acquire_uniform_workgroup +OpControlBarrier %workgroup %workgroup %acquire_release +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("For WebGPU, WorkgroupMemory must be set for Memory " + "Semantics")); +} + +TEST_F(ValidateBarriers, OpControlBarrierWebGPUUniformFailure) { + const std::string body = R"( +OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT( getDiagnosticString(), - HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics")); + HasSubstr("For WebGPU only WorkgroupMemory and AcquireRelease may be set " + "for Memory Semantics of OpControlBarrier.")); } TEST_F(ValidateBarriers, OpControlBarrierWebGPUReleaseFailure) { @@ -288,9 +305,9 @@ OpControlBarrier %workgroup %workgroup %release_uniform_workgroup CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("For WebGPU, AcquireRelease must be set for Memory " + "Semantics of OpControlBarrier")); } TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) { @@ -461,6 +478,18 @@ OpControlBarrier %subgroup %cross_device %none "cannot be CrossDevice")); } +TEST_F(ValidateBarriers, OpControlBarrierWebGPUMemoryScopeNonWorkgroup) { + const std::string body = R"( +OpControlBarrier %workgroup %subgroup %acquire_release_workgroup +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ControlBarrier: in WebGPU environment Memory Scope is " + "limited to Workgroup for OpControlBarrier")); +} + TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) { const std::string body = R"( OpControlBarrier %device %device %acquire_and_release_uniform @@ -680,13 +709,37 @@ OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0)); } -TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseSuccess) { +TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUImageMemorySuccess) { + const std::string body = R"( +OpMemoryBarrier %workgroup %image_memory +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUDeviceFailure) { + const std::string body = R"( +OpMemoryBarrier %subgroup %image_memory +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("in WebGPU environment Memory Scope is limited to " + "Workgroup for OpMemoryBarrier")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireReleaseFailure) { const std::string body = R"( OpMemoryBarrier %workgroup %acquire_release_uniform_workgroup )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageMemory must be set for Memory Semantics of " + "OpMemoryBarrier")); } TEST_F(ValidateBarriers, OpMemoryBarrierWebGPURelaxedFailure) { @@ -697,7 +750,8 @@ OpMemoryBarrier %workgroup %uniform_workgroup CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT(getDiagnosticString(), - HasSubstr("WebGPU spec requires AcquireRelease to set")); + HasSubstr("ImageMemory must be set for Memory Semantics of " + "OpMemoryBarrier")); } TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUAcquireFailure) { @@ -707,9 +761,9 @@ OpMemoryBarrier %workgroup %acquire_uniform_workgroup CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageMemory must be set for Memory Semantics of " + "OpMemoryBarrier")); } TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUReleaseFailure) { @@ -719,9 +773,21 @@ OpMemoryBarrier %workgroup %release_uniform_workgroup CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics")); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("ImageMemory must be set for Memory Semantics of " + "OpMemoryBarrier")); +} + +TEST_F(ValidateBarriers, OpMemoryBarrierWebGPUUniformFailure) { + const std::string body = R"( +OpMemoryBarrier %workgroup %uniform_image_memory +)"; + + CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("only ImageMemory may be set for Memory Semantics of " + "OpMemoryBarrier")); } TEST_F(ValidateBarriers, OpMemoryBarrierFloatMemoryScope) { diff --git a/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp b/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp index 481942fa9..24e4ac683 100644 --- a/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp +++ b/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp @@ -74,12 +74,12 @@ void PrintUsage(const char* program) { USAGE: %s [options] -o The SPIR-V binary is read from , which must have extension .spv. If - is also present, facts about the SPIR-V binary are read from this + is also present, facts about the SPIR-V binary are read from this file. The transformed SPIR-V binary is written to . Human-readable and -binary representations of the transformations that were applied to obtain this -binary are written to and , respectively. +binary representations of the transformations that were applied are written to + and , respectively. NOTE: The fuzzer is a work in progress. @@ -472,7 +472,8 @@ int main(int argc, const char** argv) { return 1; } - std::ofstream transformations_json_file(output_file_prefix + ".json"); + std::ofstream transformations_json_file(output_file_prefix + + ".transformations_json"); transformations_json_file << json_string; transformations_json_file.close(); diff --git a/3rdparty/spirv-tools/tools/opt/opt.cpp b/3rdparty/spirv-tools/tools/opt/opt.cpp index cce105385..c18b64c50 100644 --- a/3rdparty/spirv-tools/tools/opt/opt.cpp +++ b/3rdparty/spirv-tools/tools/opt/opt.cpp @@ -147,6 +147,15 @@ Options (in lexicographical order):)", around known issues with some Vulkan drivers for initialize variables.)"); printf(R"( + --descriptor-scalar-replacement + Replaces every array variable |desc| that has a DescriptorSet + and Binding decorations with a new variable for each element of + the array. Suppose |desc| was bound at binding |b|. Then the + variable corresponding to |desc[i]| will have binding |b+i|. + The descriptor set will be the same. All accesses to |desc| + must be in OpAccessChain instructions with a literal index for + the first index.)"); + printf(R"( --eliminate-dead-branches Convert conditional branches with constant condition to the indicated unconditional brranch. Delete all resulting dead @@ -206,6 +215,11 @@ Options (in lexicographical order):)", Freeze the values of specialization constants to their default values.)"); printf(R"( + --graphics-robust-access + Clamp indices used to access buffers and internal composite + values, providing guarantees that satisfy Vulkan's + robustBufferAccess rules.)"); + printf(R"( --generate-webgpu-initializers Adds initial values to OpVariable instructions that are missing them, due to their storage type requiring them for WebGPU.)");