diff --git a/3rdparty/spirv-tools/Android.mk b/3rdparty/spirv-tools/Android.mk index 8e4437893..075dc61ef 100644 --- a/3rdparty/spirv-tools/Android.mk +++ b/3rdparty/spirv-tools/Android.mk @@ -70,6 +70,7 @@ SPVTOOLS_SRC_FILES := \ source/val/validate_non_uniform.cpp \ source/val/validate_primitives.cpp \ source/val/validate_scopes.cpp \ + source/val/validate_small_type_uses.cpp \ source/val/validate_type.cpp SPVTOOLS_OPT_SRC_FILES := \ diff --git a/3rdparty/spirv-tools/BUILD.gn b/3rdparty/spirv-tools/BUILD.gn index fb9e8a4f0..3c2a9f6d5 100644 --- a/3rdparty/spirv-tools/BUILD.gn +++ b/3rdparty/spirv-tools/BUILD.gn @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import("//build_overrides/spirv_tools.gni") - -import("//testing/test.gni") import("//build_overrides/build.gni") +import("//build_overrides/spirv_tools.gni") +if (build_with_chromium) { + import("//testing/test.gni") +} spirv_headers = spirv_tools_spirv_headers_dir @@ -293,11 +294,11 @@ config("spvtools_internal_config") { source_set("spvtools_headers") { sources = [ + "include/spirv-tools/instrument.hpp", "include/spirv-tools/libspirv.h", "include/spirv-tools/libspirv.hpp", "include/spirv-tools/linker.hpp", "include/spirv-tools/optimizer.hpp", - "include/spirv-tools/instrument.hpp", ] public_configs = [ ":spvtools_public_config" ] @@ -379,11 +380,11 @@ static_library("spvtools") { ":spvtools_headers", ] - configs -= [ "//build/config/compiler:chromium_code" ] - configs += [ - "//build/config/compiler:no_chromium_code", - ":spvtools_internal_config", - ] + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + configs += [ ":spvtools_internal_config" ] } static_library("spvtools_val") { @@ -427,6 +428,7 @@ static_library("spvtools_val") { "source/val/validate_non_uniform.cpp", "source/val/validate_primitives.cpp", "source/val/validate_scopes.cpp", + "source/val/validate_small_type_uses.cpp", "source/val/validate_type.cpp", "source/val/validation_state.cpp", ] @@ -438,11 +440,11 @@ static_library("spvtools_val") { ":spvtools_headers", ] - configs -= [ "//build/config/compiler:chromium_code" ] - configs += [ - "//build/config/compiler:no_chromium_code", - ":spvtools_internal_config", - ] + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + configs += [ ":spvtools_internal_config" ] } static_library("spvtools_opt") { @@ -655,17 +657,106 @@ static_library("spvtools_opt") { ":spvtools_headers", ] - configs -= [ "//build/config/compiler:chromium_code" ] - configs += [ - "//build/config/compiler:no_chromium_code", - ":spvtools_internal_config", + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + configs += [ ":spvtools_internal_config" ] +} + +static_library("spvtools_link") { + sources = [ + "source/link/linker.cpp", ] + deps = [ + ":spvtools", + ":spvtools_val", + ] + public_deps = [ + ":spvtools_headers", + ] + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + configs += [ ":spvtools_internal_config" ] +} + +static_library("spvtools_reduce") { + sources = [ + "source/reduce/change_operand_reduction_opportunity.cpp", + "source/reduce/change_operand_reduction_opportunity.h", + "source/reduce/change_operand_to_undef_reduction_opportunity.cpp", + "source/reduce/change_operand_to_undef_reduction_opportunity.h", + "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp", + "source/reduce/conditional_branch_to_simple_conditional_branch_opportunity_finder.h", + "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.cpp", + "source/reduce/conditional_branch_to_simple_conditional_branch_reduction_opportunity.h", + "source/reduce/merge_blocks_reduction_opportunity.cpp", + "source/reduce/merge_blocks_reduction_opportunity.h", + "source/reduce/merge_blocks_reduction_opportunity_finder.cpp", + "source/reduce/merge_blocks_reduction_opportunity_finder.h", + "source/reduce/operand_to_const_reduction_opportunity_finder.cpp", + "source/reduce/operand_to_const_reduction_opportunity_finder.h", + "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.cpp", + "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h", + "source/reduce/operand_to_undef_reduction_opportunity_finder.cpp", + "source/reduce/operand_to_undef_reduction_opportunity_finder.h", + "source/reduce/reducer.cpp", + "source/reduce/reducer.h", + "source/reduce/reduction_opportunity.cpp", + "source/reduce/reduction_opportunity.h", + "source/reduce/reduction_pass.cpp", + "source/reduce/reduction_pass.h", + "source/reduce/reduction_util.cpp", + "source/reduce/reduction_util.h", + "source/reduce/remove_block_reduction_opportunity.cpp", + "source/reduce/remove_block_reduction_opportunity.h", + "source/reduce/remove_block_reduction_opportunity_finder.cpp", + "source/reduce/remove_block_reduction_opportunity_finder.h", + "source/reduce/remove_function_reduction_opportunity.cpp", + "source/reduce/remove_function_reduction_opportunity.h", + "source/reduce/remove_function_reduction_opportunity_finder.cpp", + "source/reduce/remove_function_reduction_opportunity_finder.h", + "source/reduce/remove_instruction_reduction_opportunity.cpp", + "source/reduce/remove_instruction_reduction_opportunity.h", + "source/reduce/remove_opname_instruction_reduction_opportunity_finder.cpp", + "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h", + "source/reduce/remove_selection_reduction_opportunity.cpp", + "source/reduce/remove_selection_reduction_opportunity.h", + "source/reduce/remove_selection_reduction_opportunity_finder.cpp", + "source/reduce/remove_selection_reduction_opportunity_finder.h", + "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.cpp", + "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h", + "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.cpp", + "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h", + "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.cpp", + "source/reduce/simple_conditional_branch_to_branch_reduction_opportunity.h", + "source/reduce/structured_loop_to_selection_reduction_opportunity.cpp", + "source/reduce/structured_loop_to_selection_reduction_opportunity.h", + "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.cpp", + "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h", + ] + deps = [ + ":spvtools", + ":spvtools_opt", + ] + public_deps = [ + ":spvtools_headers", + ] + if (build_with_chromium) { + configs -= [ "//build/config/compiler:chromium_code" ] + configs += [ "//build/config/compiler:no_chromium_code" ] + } + configs += [ ":spvtools_internal_config" ] } group("SPIRV-Tools") { deps = [ ":spvtools", + ":spvtools_link", ":spvtools_opt", + ":spvtools_reduce", ":spvtools_val", ] } @@ -735,12 +826,12 @@ if (build_with_chromium) { ] deps = [ - "//testing/gmock", - "//testing/gtest", - "//testing/gtest:gtest_main", ":spvtools", ":spvtools_language_header_unified1", ":spvtools_val", + "//testing/gmock", + "//testing/gtest", + "//testing/gtest:gtest_main", ] if (is_clang) { @@ -760,14 +851,129 @@ if (spirv_tools_standalone) { } } -executable("spirv-as") { +source_set("spvtools_util_cli_consumer") { + sources = [ + "tools/util/cli_consumer.cpp", + "tools/util/cli_consumer.h", + ] + configs += [ ":spvtools_internal_config" ] +} + +source_set("spvtools_software_version") { sources = [ "source/software_version.cpp", - "tools/as/as.cpp", ] deps = [ - ":spvtools", ":spvtools_build_version", ] configs += [ ":spvtools_internal_config" ] } + +executable("spirv-as") { + sources = [ + "tools/as/as.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_software_version", + ] + configs += [ ":spvtools_internal_config" ] +} + +executable("spirv-dis") { + sources = [ + "tools/dis/dis.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_software_version", + ] + configs += [ ":spvtools_internal_config" ] +} + +executable("spirv-val") { + sources = [ + "tools/val/val.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_software_version", + ":spvtools_util_cli_consumer", + ":spvtools_val", + ] + configs += [ ":spvtools_internal_config" ] +} + +executable("spirv-cfg") { + sources = [ + "tools/cfg/bin_to_dot.cpp", + "tools/cfg/bin_to_dot.h", + "tools/cfg/cfg.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_software_version", + ] + configs += [ ":spvtools_internal_config" ] +} + +executable("spirv-opt") { + sources = [ + "tools/opt/opt.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_opt", + ":spvtools_software_version", + ":spvtools_util_cli_consumer", + ":spvtools_val", + ] + configs += [ ":spvtools_internal_config" ] +} + +executable("spirv-link") { + sources = [ + "tools/link/linker.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_link", + ":spvtools_opt", + ":spvtools_software_version", + ":spvtools_val", + ] + configs += [ ":spvtools_internal_config" ] +} + +if (!is_ios) { + # iOS does not allow std::system calls which spirv-reduce requires + executable("spirv-reduce") { + sources = [ + "source/spirv_reducer_options.cpp", + "source/spirv_reducer_options.h", + "tools/reduce/reduce.cpp", + ] + deps = [ + ":spvtools", + ":spvtools_reduce", + ":spvtools_software_version", + ":spvtools_util_cli_consumer", + ":spvtools_val", + ] + configs += [ ":spvtools_internal_config" ] + } +} + +group("all_spirv_tools") { + deps = [ + ":spirv-as", + ":spirv-cfg", + ":spirv-dis", + ":spirv-link", + ":spirv-opt", + ":spirv-val", + ] + if (!is_ios) { + deps += [ ":spirv-reduce" ] + } +} diff --git a/3rdparty/spirv-tools/DEPS b/3rdparty/spirv-tools/DEPS index f4557e971..70685ad80 100644 --- a/3rdparty/spirv-tools/DEPS +++ b/3rdparty/spirv-tools/DEPS @@ -5,8 +5,8 @@ vars = { 'effcee_revision': 'b83b58d177b797edd1f94c5f10837f2cc2863f0a', 'googletest_revision': '2f42d769ad1b08742f7ccb5ad4dd357fc5ff248c', - 're2_revision': '848dfb7e1d7ba641d598cb66f81590f3999a555a', - 'spirv_headers_revision': 'de99d4d834aeb51dd9f099baa285bd44fd04bb3d', + 're2_revision': 'e356bd3f80e0c15c1050323bb5a2d0f8ea4845f4', + 'spirv_headers_revision': '123dc278f204f8e833e1a88d31c46d0edf81d4b2', } deps = { diff --git a/3rdparty/spirv-tools/include/generated/build-version.inc b/3rdparty/spirv-tools/include/generated/build-version.inc index 2729a0065..25ecd73e3 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-67-g9702d47c" +"v2019.4-dev", "SPIRV-Tools v2019.4-dev v2019.3-82-g55adf4cf" diff --git a/3rdparty/spirv-tools/include/generated/generators.inc b/3rdparty/spirv-tools/include/generated/generators.inc index c1da269bf..096a5630e 100644 --- a/3rdparty/spirv-tools/include/generated/generators.inc +++ b/3rdparty/spirv-tools/include/generated/generators.inc @@ -20,3 +20,4 @@ {19, "Clay", "Clay Shader Compiler", "Clay Clay Shader Compiler"}, {20, "W3C WebGPU Group", "WHLSL Shader Translator", "W3C WebGPU Group WHLSL Shader Translator"}, {21, "Google", "Clspv", "Google Clspv"}, +{22, "Google", "MLIR SPIR-V Serializer", "Google MLIR SPIR-V Serializer"}, diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h index 82717e910..e21b05820 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.h +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.h @@ -574,6 +574,15 @@ SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetValidatorOptions( SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetMaxIdBound( spv_optimizer_options options, uint32_t val); +// Records whether all bindings within the module should be preserved. +SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings( + spv_optimizer_options options, bool val); + +// Records whether all specialization constants within the module +// should be preserved. +SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants( + spv_optimizer_options options, bool val); + // Creates a reducer options object with default options. Returns a valid // options object. The object remains valid until it is passed into // |spvReducerOptionsDestroy|. @@ -607,6 +616,11 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsDestroy(spv_fuzzer_options options); SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed( spv_fuzzer_options options, uint32_t seed); +// Sets the maximum number of steps that the shrinker should take before giving +// up. +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit( + spv_fuzzer_options options, uint32_t shrinker_step_limit); + // Encodes the given SPIR-V assembly text to its binary representation. The // length parameter specifies the number of bytes for text. Encoded binary will // be stored into *binary. Any error will be written into *diagnostic if diff --git a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp index b0fd4a2c2..2da1152aa 100644 --- a/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp +++ b/3rdparty/spirv-tools/include/spirv-tools/libspirv.hpp @@ -161,6 +161,18 @@ class OptimizerOptions { spvOptimizerOptionsSetMaxIdBound(options_, new_bound); } + // Records whether all bindings within the module should be preserved. + void set_preserve_bindings(bool preserve_bindings) { + spvOptimizerOptionsSetPreserveBindings(options_, preserve_bindings); + } + + // Records whether all specialization constants within the module + // should be preserved. + void set_preserve_spec_constants(bool preserve_spec_constants) { + spvOptimizerOptionsSetPreserveSpecConstants(options_, + preserve_spec_constants); + } + private: spv_optimizer_options options_; }; @@ -207,6 +219,11 @@ class FuzzerOptions { spvFuzzerOptionsSetRandomSeed(options_, seed); } + // See spvFuzzerOptionsSetShrinkerStepLimit. + void set_shrinker_step_limit(uint32_t shrinker_step_limit) { + spvFuzzerOptionsSetShrinkerStepLimit(options_, shrinker_step_limit); + } + private: spv_fuzzer_options options_; }; diff --git a/3rdparty/spirv-tools/source/CMakeLists.txt b/3rdparty/spirv-tools/source/CMakeLists.txt index bd4c465d9..cb63ff0a2 100644 --- a/3rdparty/spirv-tools/source/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/CMakeLists.txt @@ -304,6 +304,7 @@ set(SPIRV_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_non_uniform.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_primitives.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_scopes.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_small_type_uses.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/validate_type.cpp ${CMAKE_CURRENT_SOURCE_DIR}/val/decoration.h ${CMAKE_CURRENT_SOURCE_DIR}/val/basic_block.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt index 2e065b72c..75e212515 100644 --- a/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/source/fuzz/CMakeLists.txt @@ -41,6 +41,7 @@ if(SPIRV_BUILD_FUZZER) pseudo_random_generator.h random_generator.h replayer.h + shrinker.h transformation.h transformation_add_constant_boolean.h transformation_add_constant_scalar.h @@ -70,6 +71,7 @@ if(SPIRV_BUILD_FUZZER) pseudo_random_generator.cpp random_generator.cpp replayer.cpp + shrinker.cpp transformation.cpp transformation_add_constant_boolean.cpp transformation_add_constant_scalar.cpp diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp index 606ccd671..0fa645ab8 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.cpp @@ -55,14 +55,14 @@ void Fuzzer::SetMessageConsumer(MessageConsumer c) { Fuzzer::FuzzerResultStatus Fuzzer::Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, - std::vector* binary_out, - protobufs::TransformationSequence* transformation_sequence_out, - spv_const_fuzzer_options options) const { + spv_const_fuzzer_options options, std::vector* binary_out, + protobufs::TransformationSequence* transformation_sequence_out) const { // Check compatibility between the library version being linked with and the // header files being used. GOOGLE_PROTOBUF_VERIFY_VERSION; spvtools::SpirvTools tools(impl_->target_env); + tools.SetMessageConsumer(impl_->consumer); if (!tools.IsValid()) { impl_->consumer(SPV_MSG_ERROR, nullptr, {}, "Failed to create SPIRV-Tools interface; stopping."); diff --git a/3rdparty/spirv-tools/source/fuzz/fuzzer.h b/3rdparty/spirv-tools/source/fuzz/fuzzer.h index af45d9b2b..a257c2a19 100644 --- a/3rdparty/spirv-tools/source/fuzz/fuzzer.h +++ b/3rdparty/spirv-tools/source/fuzz/fuzzer.h @@ -58,9 +58,8 @@ class Fuzzer { FuzzerResultStatus Run( const std::vector& binary_in, const protobufs::FactSequence& initial_facts, - std::vector* binary_out, - protobufs::TransformationSequence* transformation_sequence_out, - spv_const_fuzzer_options options) const; + spv_const_fuzzer_options options, std::vector* binary_out, + protobufs::TransformationSequence* transformation_sequence_out) const; private: struct Impl; // Opaque struct for holding internal data. diff --git a/3rdparty/spirv-tools/source/fuzz/shrinker.cpp b/3rdparty/spirv-tools/source/fuzz/shrinker.cpp new file mode 100644 index 000000000..f8d8aa309 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/shrinker.cpp @@ -0,0 +1,240 @@ +// 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/shrinker.h" + +#include + +#include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/replayer.h" +#include "source/spirv_fuzzer_options.h" +#include "source/util/make_unique.h" + +namespace spvtools { +namespace fuzz { + +namespace { + +// A helper to get the size of a protobuf transformation sequence in a less +// verbose manner. +uint32_t NumRemainingTransformations( + const protobufs::TransformationSequence& transformation_sequence) { + return static_cast(transformation_sequence.transformation_size()); +} + +// A helper to return a transformation sequence identical to |transformations|, +// except that a chunk of size |chunk_size| starting from |chunk_index| x +// |chunk_size| is removed (or as many transformations as available if the whole +// chunk is not). +protobufs::TransformationSequence RemoveChunk( + const protobufs::TransformationSequence& transformations, + uint32_t chunk_index, uint32_t chunk_size) { + uint32_t lower = chunk_index * chunk_size; + uint32_t upper = std::min((chunk_index + 1) * chunk_size, + NumRemainingTransformations(transformations)); + assert(lower < upper); + assert(upper <= NumRemainingTransformations(transformations)); + protobufs::TransformationSequence result; + for (uint32_t j = 0; j < NumRemainingTransformations(transformations); j++) { + if (j >= lower && j < upper) { + continue; + } + protobufs::Transformation transformation = + transformations.transformation()[j]; + *result.mutable_transformation()->Add() = transformation; + } + return result; +} + +} // namespace + +struct Shrinker::Impl { + explicit Impl(spv_target_env env, uint32_t limit) + : target_env(env), step_limit(limit) {} + + const spv_target_env target_env; // Target environment. + MessageConsumer consumer; // Message consumer. + const uint32_t step_limit; // Step limit for reductions. +}; + +Shrinker::Shrinker(spv_target_env env, uint32_t step_limit) + : impl_(MakeUnique(env, step_limit)) {} + +Shrinker::~Shrinker() = default; + +void Shrinker::SetMessageConsumer(MessageConsumer c) { + impl_->consumer = std::move(c); +} + +Shrinker::ShrinkerResultStatus Shrinker::Run( + const std::vector& binary_in, + const protobufs::FactSequence& initial_facts, + const protobufs::TransformationSequence& transformation_sequence_in, + const Shrinker::InterestingnessFunction& interestingness_function, + std::vector* binary_out, + protobufs::TransformationSequence* transformation_sequence_out) const { + // Check compatibility between the library version being linked with and the + // header files being used. + GOOGLE_PROTOBUF_VERIFY_VERSION; + + spvtools::SpirvTools tools(impl_->target_env); + if (!tools.IsValid()) { + impl_->consumer(SPV_MSG_ERROR, nullptr, {}, + "Failed to create SPIRV-Tools interface; stopping."); + return Shrinker::ShrinkerResultStatus::kFailedToCreateSpirvToolsInterface; + } + + // Initial binary should be valid. + if (!tools.Validate(&binary_in[0], binary_in.size())) { + impl_->consumer(SPV_MSG_INFO, nullptr, {}, + "Initial binary is invalid; stopping."); + return Shrinker::ShrinkerResultStatus::kInitialBinaryInvalid; + } + + std::vector current_best_binary; + protobufs::TransformationSequence current_best_transformations; + + // Run a replay of the initial transformation sequence to (a) check that it + // succeeds, (b) get the binary that results from running these + // transformations, and (c) get the subsequence of the initial transformations + // that actually apply (in principle this could be a strict subsequence). + if (Replayer(impl_->target_env) + .Run(binary_in, initial_facts, transformation_sequence_in, + ¤t_best_binary, ¤t_best_transformations) != + Replayer::ReplayerResultStatus::kComplete) { + return ShrinkerResultStatus::kReplayFailed; + } + + // Check that the binary produced by applying the initial transformations is + // indeed interesting. + if (!interestingness_function(current_best_binary, 0)) { + impl_->consumer(SPV_MSG_INFO, nullptr, {}, + "Initial binary is not interesting; stopping."); + return ShrinkerResultStatus::kInitialBinaryNotInteresting; + } + + uint32_t attempt = 0; // Keeps track of the number of shrink attempts that + // have been tried, whether successful or not. + + uint32_t chunk_size = + std::max(1u, NumRemainingTransformations(current_best_transformations) / + 2); // The number of contiguous transformations that the + // shrinker will try to remove in one go; starts + // high and decreases during the shrinking process. + + // Keep shrinking until we: + // - reach the step limit, + // - run out of transformations to remove, or + // - cannot make the chunk size any smaller. + while (attempt < impl_->step_limit && + !current_best_transformations.transformation().empty() && + chunk_size > 0) { + bool progress_this_round = + false; // Used to decide whether to make the chunk size with which we + // remove transformations smaller. If we managed to remove at + // least one chunk of transformations at a particular chunk + // size, we set this flag so that we do not yet decrease the + // chunk size. + + assert(chunk_size <= + NumRemainingTransformations(current_best_transformations) && + "Chunk size should never exceed the number of transformations that " + "remain."); + + // The number of chunks is the ceiling of (#remaining_transformations / + // chunk_size). + const uint32_t num_chunks = + (NumRemainingTransformations(current_best_transformations) + + chunk_size - 1) / + chunk_size; + assert(num_chunks >= 1 && "There should be at least one chunk."); + assert(num_chunks * chunk_size >= + NumRemainingTransformations(current_best_transformations) && + "All transformations should be in some chunk."); + + // We go through the transformations in reverse, in chunks of size + // |chunk_size|, using |chunk_index| to track which chunk to try removing + // next. The loop exits early if we reach the shrinking step limit. + for (int chunk_index = num_chunks - 1; + attempt < impl_->step_limit && chunk_index >= 0; chunk_index--) { + // Remove a chunk of transformations according to the current index and + // chunk size. + auto transformations_with_chunk_removed = + RemoveChunk(current_best_transformations, chunk_index, chunk_size); + + // Replay the smaller sequence of transformations to get a next binary and + // transformation sequence. Note that the transformations arising from + // replay might be even smaller than the transformations with the chunk + // removed, because removing those transformations might make further + // transformations inapplicable. + std::vector next_binary; + protobufs::TransformationSequence next_transformation_sequence; + if (Replayer(impl_->target_env) + .Run(binary_in, initial_facts, transformations_with_chunk_removed, + &next_binary, &next_transformation_sequence) != + Replayer::ReplayerResultStatus::kComplete) { + // Replay should not fail; if it does, we need to abort shrinking. + return ShrinkerResultStatus::kReplayFailed; + } + + assert(NumRemainingTransformations(next_transformation_sequence) >= + chunk_index * chunk_size && + "Removing this chunk of transformations should not have an effect " + "on earlier chunks."); + + if (interestingness_function(next_binary, attempt)) { + // If the binary arising from the smaller transformation sequence is + // interesting, this becomes our current best binary and transformation + // sequence. + current_best_binary = next_binary; + current_best_transformations = next_transformation_sequence; + progress_this_round = true; + } + // Either way, this was a shrink attempt, so increment our count of shrink + // attempts. + attempt++; + } + if (!progress_this_round) { + // If we didn't manage to remove any chunks at this chunk size, try a + // smaller chunk size. + chunk_size /= 2; + } + // Decrease the chunk size until it becomes no larger than the number of + // remaining transformations. + while (chunk_size > + NumRemainingTransformations(current_best_transformations)) { + chunk_size /= 2; + } + } + + // The output from the shrinker is the best binary we saw, and the + // transformations that led to it. + *binary_out = current_best_binary; + *transformation_sequence_out = current_best_transformations; + + // Indicate whether shrinking completed or was truncated due to reaching the + // step limit. + assert(attempt <= impl_->step_limit); + if (attempt == impl_->step_limit) { + std::stringstream strstream; + strstream << "Shrinking did not complete; step limit " << impl_->step_limit + << " was reached."; + impl_->consumer(SPV_MSG_WARNING, nullptr, {}, strstream.str().c_str()); + return Shrinker::ShrinkerResultStatus::kStepLimitReached; + } + return Shrinker::ShrinkerResultStatus::kComplete; +} + +} // namespace fuzz +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/fuzz/shrinker.h b/3rdparty/spirv-tools/source/fuzz/shrinker.h new file mode 100644 index 000000000..72dd470b0 --- /dev/null +++ b/3rdparty/spirv-tools/source/fuzz/shrinker.h @@ -0,0 +1,91 @@ +// 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_SHRINKER_H_ +#define SOURCE_FUZZ_SHRINKER_H_ + +#include +#include + +#include "source/fuzz/protobufs/spirvfuzz_protobufs.h" +#include "spirv-tools/libspirv.hpp" + +namespace spvtools { +namespace fuzz { + +// Shrinks a sequence of transformations that lead to an interesting SPIR-V +// binary to yield a smaller sequence of transformations that still produce an +// interesting binary. +class Shrinker { + public: + // Possible statuses that can result from running the shrinker. + enum ShrinkerResultStatus { + kComplete, + kFailedToCreateSpirvToolsInterface, + kInitialBinaryInvalid, + kInitialBinaryNotInteresting, + kReplayFailed, + kStepLimitReached, + }; + + // The type for a function that will take a binary, |binary|, and return true + // if and only if the binary is deemed interesting. (The function also takes + // an integer argument, |counter|, that will be incremented each time the + // function is called; this is for debugging purposes). + // + // The notion of "interesting" depends on what properties of the binary or + // tools that process the binary we are trying to maintain during shrinking. + using InterestingnessFunction = std::function& binary, uint32_t counter)>; + + // Constructs a shrinker from the given target environment. + Shrinker(spv_target_env env, uint32_t step_limit); + + // Disables copy/move constructor/assignment operations. + Shrinker(const Shrinker&) = delete; + Shrinker(Shrinker&&) = delete; + Shrinker& operator=(const Shrinker&) = delete; + Shrinker& operator=(Shrinker&&) = delete; + + ~Shrinker(); + + // Sets the message consumer to the given |consumer|. The |consumer| will be + // invoked once for each message communicated from the library. + void SetMessageConsumer(MessageConsumer consumer); + + // Requires that when |transformation_sequence_in| is applied to |binary_in| + // with initial facts |initial_facts|, the resulting binary is interesting + // according to |interestingness_function|. + // + // Produces, via |transformation_sequence_out|, a subsequence of + // |transformation_sequence_in| that, when applied with initial facts + // |initial_facts|, produces a binary (captured via |binary_out|) that is + // also interesting according to |interestingness_function|. + ShrinkerResultStatus Run( + const std::vector& binary_in, + const protobufs::FactSequence& initial_facts, + const protobufs::TransformationSequence& transformation_sequence_in, + const InterestingnessFunction& interestingness_function, + std::vector* binary_out, + protobufs::TransformationSequence* transformation_sequence_out) const; + + private: + struct Impl; // Opaque struct for holding internal data. + std::unique_ptr impl_; // Unique pointer to internal data. +}; + +} // namespace fuzz +} // namespace spvtools + +#endif // SOURCE_FUZZ_SHRINKER_H_ 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 a4bef6c9f..bc7ec874b 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 @@ -570,13 +570,28 @@ void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() { AddToWorklist(&entry); } } - // Keep workgroup size. for (auto& anno : get_module()->annotations()) { if (anno.opcode() == SpvOpDecorate) { + // Keep workgroup size. if (anno.GetSingleWordInOperand(1u) == SpvDecorationBuiltIn && anno.GetSingleWordInOperand(2u) == SpvBuiltInWorkgroupSize) { AddToWorklist(&anno); } + + if (context()->preserve_bindings()) { + // Keep all bindings. + if ((anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet) || + (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)) { + AddToWorklist(&anno); + } + } + + if (context()->preserve_spec_constants()) { + // Keep all specialization constant instructions + if (anno.GetSingleWordInOperand(1u) == SpvDecorationSpecId) { + AddToWorklist(&anno); + } + } } } } diff --git a/3rdparty/spirv-tools/source/opt/constants.h b/3rdparty/spirv-tools/source/opt/constants.h index d62d55208..8ac4ce630 100644 --- a/3rdparty/spirv-tools/source/opt/constants.h +++ b/3rdparty/spirv-tools/source/opt/constants.h @@ -432,7 +432,7 @@ class NullConstant : public Constant { std::unique_ptr Copy() const override { return std::unique_ptr(CopyNullConstant().release()); } - bool IsZero() const override { return true; }; + bool IsZero() const override { return true; } }; // Hash function for Constant instances. Use the structure of the constant as diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp index 68c0f22f4..8425a398c 100644 --- a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.cpp @@ -93,8 +93,7 @@ BasicBlock* DeadBranchElimPass::GetParentBlock(uint32_t id) { bool DeadBranchElimPass::MarkLiveBlocks( Function* func, std::unordered_set* live_blocks) { - StructuredCFGAnalysis* cfgAnalysis = context()->GetStructuredCFGAnalysis(); - + std::vector> conditions_to_simplify; std::unordered_set blocks_with_backedge; std::vector stack; stack.push_back(&*func->begin()); @@ -169,44 +168,7 @@ bool DeadBranchElimPass::MarkLiveBlocks( if (simplify) { modified = true; - // Replace branch with a simpler branch. - // Fix up the merge instruction if it is a selection merge. - Instruction* mergeInst = block->GetMergeInst(); - if (mergeInst && mergeInst->opcode() == SpvOpSelectionMerge) { - if (mergeInst->NextNode()->opcode() == SpvOpSwitch && - SwitchHasNestedBreak(block->id())) { - // We have to keep the switch because it has a nest break, so we - // remove all cases except for the live one. - Instruction::OperandList new_operands; - new_operands.push_back(terminator->GetInOperand(0)); - new_operands.push_back({SPV_OPERAND_TYPE_ID, {live_lab_id}}); - terminator->SetInOperands(std::move(new_operands)); - context()->UpdateDefUse(terminator); - } else { - // Check if the merge instruction is still needed because of a - // non-nested break from the construct. Move the merge instruction if - // it is still needed. - Instruction* first_break = FindFirstExitFromSelectionMerge( - live_lab_id, mergeInst->GetSingleWordInOperand(0), - cfgAnalysis->LoopMergeBlock(live_lab_id), - cfgAnalysis->LoopContinueBlock(live_lab_id), - cfgAnalysis->SwitchMergeBlock(live_lab_id)); - - AddBranch(live_lab_id, block); - context()->KillInst(terminator); - if (first_break == nullptr) { - context()->KillInst(mergeInst); - } else { - mergeInst->RemoveFromList(); - first_break->InsertBefore(std::unique_ptr(mergeInst)); - context()->set_instr_block(mergeInst, - context()->get_instr_block(first_break)); - } - } - } else { - AddBranch(live_lab_id, block); - context()->KillInst(terminator); - } + conditions_to_simplify.push_back({block, live_lab_id}); stack.push_back(GetParentBlock(live_lab_id)); } else { // All successors are live. @@ -217,9 +179,60 @@ bool DeadBranchElimPass::MarkLiveBlocks( } } + // Traverse |conditions_to_simplify in reverse order. This is done so that we + // simplify nested constructs before simplifying the constructs that contain + // them. + for (auto b = conditions_to_simplify.rbegin(); + b != conditions_to_simplify.rend(); ++b) { + SimplifyBranch(b->first, b->second); + } + return modified; } +void DeadBranchElimPass::SimplifyBranch(BasicBlock* block, + uint32_t live_lab_id) { + Instruction* merge_inst = block->GetMergeInst(); + Instruction* terminator = block->terminator(); + if (merge_inst && merge_inst->opcode() == SpvOpSelectionMerge) { + if (merge_inst->NextNode()->opcode() == SpvOpSwitch && + SwitchHasNestedBreak(block->id())) { + // We have to keep the switch because it has a nest break, so we + // remove all cases except for the live one. + Instruction::OperandList new_operands; + new_operands.push_back(terminator->GetInOperand(0)); + new_operands.push_back({SPV_OPERAND_TYPE_ID, {live_lab_id}}); + terminator->SetInOperands(move(new_operands)); + context()->UpdateDefUse(terminator); + } else { + // Check if the merge instruction is still needed because of a + // non-nested break from the construct. Move the merge instruction if + // it is still needed. + StructuredCFGAnalysis* cfg_analysis = + context()->GetStructuredCFGAnalysis(); + Instruction* first_break = FindFirstExitFromSelectionMerge( + live_lab_id, merge_inst->GetSingleWordInOperand(0), + cfg_analysis->LoopMergeBlock(live_lab_id), + cfg_analysis->LoopContinueBlock(live_lab_id), + cfg_analysis->SwitchMergeBlock(live_lab_id)); + + AddBranch(live_lab_id, block); + context()->KillInst(terminator); + if (first_break == nullptr) { + context()->KillInst(merge_inst); + } else { + merge_inst->RemoveFromList(); + first_break->InsertBefore(std::unique_ptr(merge_inst)); + context()->set_instr_block(merge_inst, + context()->get_instr_block(first_break)); + } + } + } else { + AddBranch(live_lab_id, block); + context()->KillInst(terminator); + } +} + void DeadBranchElimPass::MarkUnreachableStructuredTargets( const std::unordered_set& live_blocks, std::unordered_set* unreachable_merges, diff --git a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h index eadf04d8d..a50933fdb 100644 --- a/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h +++ b/3rdparty/spirv-tools/source/opt/dead_branch_elim_pass.h @@ -161,6 +161,12 @@ class DeadBranchElimPass : public MemPass { // Returns true if there is a brach to the merge node of the selection // construct |switch_header_id| that is inside a nested selection construct. bool SwitchHasNestedBreak(uint32_t switch_header_id); + + // Replaces the terminator of |block| with a branch to |live_lab_id|. The + // merge instruction is deleted or moved as needed to maintain structured + // control flow. Assumes that the StructuredCFGAnalysis is valid for the + // constructs containing |block|. + void SimplifyBranch(BasicBlock* block, uint32_t live_lab_id); }; } // namespace opt diff --git a/3rdparty/spirv-tools/source/opt/decoration_manager.cpp b/3rdparty/spirv-tools/source/opt/decoration_manager.cpp index a12326ba5..a10c992e1 100644 --- a/3rdparty/spirv-tools/source/opt/decoration_manager.cpp +++ b/3rdparty/spirv-tools/source/opt/decoration_manager.cpp @@ -22,6 +22,33 @@ #include "source/opt/ir_context.h" +namespace { +using InstructionVector = std::vector; +using DecorationSet = std::set; + +// Returns true if |a| is a subet of |b|. +bool IsSubset(const DecorationSet& a, const DecorationSet& b) { + auto it1 = a.begin(); + auto it2 = b.begin(); + + while (it1 != a.end()) { + if (it2 == b.end() || *it1 < *it2) { + // |*it1| is in |a|, but not in |b|. + return false; + } + if (*it1 == *it2) { + // Found the element move to the next one. + it1++; + it2++; + } else /* *it1 > *it2 */ { + // Did not find |*it1| yet, check the next element in |b|. + it2++; + } + } + return true; +} +} // namespace + namespace spvtools { namespace opt { namespace analysis { @@ -160,18 +187,15 @@ std::vector DecorationManager::GetDecorationsFor( bool DecorationManager::HaveTheSameDecorations(uint32_t id1, uint32_t id2) const { - using InstructionList = std::vector; - using DecorationSet = std::set; - - const InstructionList decorations_for1 = GetDecorationsFor(id1, false); - const InstructionList decorations_for2 = GetDecorationsFor(id2, false); + const InstructionVector decorations_for1 = GetDecorationsFor(id1, false); + const InstructionVector decorations_for2 = GetDecorationsFor(id2, false); // This function splits the decoration instructions into different sets, // based on their opcode; only OpDecorate, OpDecorateId, // OpDecorateStringGOOGLE, and OpMemberDecorate are considered, the other // opcodes are ignored. const auto fillDecorationSets = - [](const InstructionList& decoration_list, DecorationSet* decorate_set, + [](const InstructionVector& decoration_list, DecorationSet* decorate_set, DecorationSet* decorate_id_set, DecorationSet* decorate_string_set, DecorationSet* member_decorate_set) { for (const Instruction* inst : decoration_list) { @@ -227,6 +251,73 @@ bool DecorationManager::HaveTheSameDecorations(uint32_t id1, return result; } +bool DecorationManager::HaveSubsetOfDecorations(uint32_t id1, + uint32_t id2) const { + const InstructionVector decorations_for1 = GetDecorationsFor(id1, false); + const InstructionVector decorations_for2 = GetDecorationsFor(id2, false); + + // This function splits the decoration instructions into different sets, + // based on their opcode; only OpDecorate, OpDecorateId, + // OpDecorateStringGOOGLE, and OpMemberDecorate are considered, the other + // opcodes are ignored. + const auto fillDecorationSets = + [](const InstructionVector& decoration_list, DecorationSet* decorate_set, + DecorationSet* decorate_id_set, DecorationSet* decorate_string_set, + DecorationSet* member_decorate_set) { + for (const Instruction* inst : decoration_list) { + std::u32string decoration_payload; + // Ignore the opcode and the target as we do not want them to be + // compared. + for (uint32_t i = 1u; i < inst->NumInOperands(); ++i) { + for (uint32_t word : inst->GetInOperand(i).words) { + decoration_payload.push_back(word); + } + } + + switch (inst->opcode()) { + case SpvOpDecorate: + decorate_set->emplace(std::move(decoration_payload)); + break; + case SpvOpMemberDecorate: + member_decorate_set->emplace(std::move(decoration_payload)); + break; + case SpvOpDecorateId: + decorate_id_set->emplace(std::move(decoration_payload)); + break; + case SpvOpDecorateStringGOOGLE: + decorate_string_set->emplace(std::move(decoration_payload)); + break; + default: + break; + } + } + }; + + DecorationSet decorate_set_for1; + DecorationSet decorate_id_set_for1; + DecorationSet decorate_string_set_for1; + DecorationSet member_decorate_set_for1; + fillDecorationSets(decorations_for1, &decorate_set_for1, + &decorate_id_set_for1, &decorate_string_set_for1, + &member_decorate_set_for1); + + DecorationSet decorate_set_for2; + DecorationSet decorate_id_set_for2; + DecorationSet decorate_string_set_for2; + DecorationSet member_decorate_set_for2; + fillDecorationSets(decorations_for2, &decorate_set_for2, + &decorate_id_set_for2, &decorate_string_set_for2, + &member_decorate_set_for2); + + const bool result = + IsSubset(decorate_set_for1, decorate_set_for2) && + IsSubset(decorate_id_set_for1, decorate_id_set_for2) && + IsSubset(member_decorate_set_for1, member_decorate_set_for2) && + // Compare string sets last in case the strings are long. + IsSubset(decorate_string_set_for1, decorate_string_set_for2); + return result; +} + // TODO(pierremoreau): If OpDecorateId is referencing an OpConstant, one could // check that the constants are the same rather than just // looking at the constant ID. diff --git a/3rdparty/spirv-tools/source/opt/decoration_manager.h b/3rdparty/spirv-tools/source/opt/decoration_manager.h index a5fb4c861..01244f293 100644 --- a/3rdparty/spirv-tools/source/opt/decoration_manager.h +++ b/3rdparty/spirv-tools/source/opt/decoration_manager.h @@ -74,6 +74,12 @@ class DecorationManager { // instructions that apply the same decorations but to different IDs, still // count as being the same. bool HaveTheSameDecorations(uint32_t id1, uint32_t id2) const; + + // Returns whether two IDs have the same decorations. Two SpvOpGroupDecorate + // instructions that apply the same decorations but to different IDs, still + // count as being the same. + bool HaveSubsetOfDecorations(uint32_t id1, uint32_t id2) const; + // Returns whether the two decorations instructions are the same and are // applying the same decorations; unless |ignore_target| is false, the targets // to which they are applied to does not matter, except for the member part. diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.cpp b/3rdparty/spirv-tools/source/opt/inline_pass.cpp index f348bbe3e..01ed5b88e 100644 --- a/3rdparty/spirv-tools/source/opt/inline_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/inline_pass.cpp @@ -629,12 +629,39 @@ bool InlinePass::GenInlineCode( return true; } -bool InlinePass::IsInlinableFunctionCall(const Instruction* inst) { +bool InlinePass::IsInlinableFunctionCall(Instruction* inst) { if (inst->opcode() != SpvOp::SpvOpFunctionCall) return false; const uint32_t calleeFnId = inst->GetSingleWordOperand(kSpvFunctionCallFunctionId); const auto ci = inlinable_.find(calleeFnId); - return ci != inlinable_.cend(); + if (ci == inlinable_.cend()) { + return false; + } + + if (funcs_with_opkill_.count(calleeFnId) == 0) { + return true; + } + + // We cannot inline into a continue construct if the function has an OpKill. + auto* cfg_analysis = context()->GetStructuredCFGAnalysis(); + BasicBlock* bb = context()->get_instr_block(inst); + uint32_t loop_header_id = cfg_analysis->ContainingLoop(bb->id()); + if (loop_header_id == 0) { + // Not in a loop, so we can inline. + return true; + } + BasicBlock* loop_header_bb = context()->get_instr_block(loop_header_id); + uint32_t loop_continue = + loop_header_bb->GetLoopMergeInst()->GetSingleWordOperand(1); + + Function* caller_func = bb->GetParent(); + DominatorAnalysis* dom = context()->GetDominatorAnalysis(caller_func); + if (dom->Dominates(loop_continue, bb->id())) { + // The function call is the continue construct and the callee contains an + // OpKill. + return false; + } + return true; } void InlinePass::UpdateSucceedingPhis( @@ -711,6 +738,9 @@ bool InlinePass::IsInlinableFunction(Function* func) { // the returns as a branch to the loop's merge block. However, this can only // done validly if the return was not in a loop in the original function. // Also remember functions with multiple (early) returns. + + // Do not inline functions with an OpKill because they may be inlined into a + // continue construct. AnalyzeReturns(func); if (no_return_in_loop_.find(func->result_id()) == no_return_in_loop_.cend()) { return false; @@ -741,6 +771,13 @@ void InlinePass::InitializeInline() { } // Compute inlinability if (IsInlinableFunction(&fn)) inlinable_.insert(fn.result_id()); + + bool has_opkill = !fn.WhileEachInst( + [](Instruction* inst) { return inst->opcode() != SpvOpKill; }); + + if (has_opkill) { + funcs_with_opkill_.insert(fn.result_id()); + } } } diff --git a/3rdparty/spirv-tools/source/opt/inline_pass.h b/3rdparty/spirv-tools/source/opt/inline_pass.h index ecfe964f1..e17dddb60 100644 --- a/3rdparty/spirv-tools/source/opt/inline_pass.h +++ b/3rdparty/spirv-tools/source/opt/inline_pass.h @@ -122,7 +122,7 @@ class InlinePass : public Pass { UptrVectorIterator call_block_itr); // Return true if |inst| is a function call that can be inlined. - bool IsInlinableFunctionCall(const Instruction* inst); + bool IsInlinableFunctionCall(Instruction* inst); // Return true if |func| does not have a return that is // nested in a structured if, switch or loop. @@ -159,6 +159,9 @@ class InlinePass : public Pass { // Set of ids of functions with no returns in loop std::set no_return_in_loop_; + // Set of ids of functions with no returns in loop + std::unordered_set funcs_with_opkill_; + // Set of ids of inlinable functions std::set inlinable_; diff --git a/3rdparty/spirv-tools/source/opt/ir_context.h b/3rdparty/spirv-tools/source/opt/ir_context.h index 32d5b1791..37c6449c2 100644 --- a/3rdparty/spirv-tools/source/opt/ir_context.h +++ b/3rdparty/spirv-tools/source/opt/ir_context.h @@ -100,7 +100,9 @@ class IRContext { constant_mgr_(nullptr), type_mgr_(nullptr), id_to_name_(nullptr), - max_id_bound_(kDefaultMaxIdBound) { + max_id_bound_(kDefaultMaxIdBound), + preserve_bindings_(false), + preserve_spec_constants_(false) { SetContextMessageConsumer(syntax_context_, consumer_); module_->SetContext(this); } @@ -115,7 +117,9 @@ class IRContext { valid_analyses_(kAnalysisNone), type_mgr_(nullptr), id_to_name_(nullptr), - max_id_bound_(kDefaultMaxIdBound) { + max_id_bound_(kDefaultMaxIdBound), + preserve_bindings_(false), + preserve_spec_constants_(false) { SetContextMessageConsumer(syntax_context_, consumer_); module_->SetContext(this); InitializeCombinators(); @@ -491,6 +495,16 @@ class IRContext { uint32_t max_id_bound() const { return max_id_bound_; } void set_max_id_bound(uint32_t new_bound) { max_id_bound_ = new_bound; } + bool preserve_bindings() const { return preserve_bindings_; } + void set_preserve_bindings(bool should_preserve_bindings) { + preserve_bindings_ = should_preserve_bindings; + } + + bool preserve_spec_constants() const { return preserve_spec_constants_; } + void set_preserve_spec_constants(bool should_preserve_spec_constants) { + preserve_spec_constants_ = should_preserve_spec_constants; + } + // Return id of input variable only decorated with |builtin|, if in module. // Create variable and return its id otherwise. If builtin not currently // supported, return 0. @@ -750,6 +764,13 @@ class IRContext { // The maximum legal value for the id bound. uint32_t max_id_bound_; + + // Whether all bindings within |module_| should be preserved. + bool preserve_bindings_; + + // Whether all specialization constants within |module_| + // should be preserved. + bool preserve_spec_constants_; }; inline IRContext::Analysis operator|(IRContext::Analysis lhs, diff --git a/3rdparty/spirv-tools/source/opt/loop_dependence.h b/3rdparty/spirv-tools/source/opt/loop_dependence.h index 582c8d0ac..03a9075fe 100644 --- a/3rdparty/spirv-tools/source/opt/loop_dependence.h +++ b/3rdparty/spirv-tools/source/opt/loop_dependence.h @@ -181,19 +181,21 @@ class Constraint { bool operator!=(const Constraint& other) const; +// clang-format off #define DeclareCastMethod(target) \ virtual target* As##target() { return nullptr; } \ virtual const target* As##target() const { return nullptr; } - DeclareCastMethod(DependenceLine); - DeclareCastMethod(DependenceDistance); - DeclareCastMethod(DependencePoint); - DeclareCastMethod(DependenceNone); - DeclareCastMethod(DependenceEmpty); + DeclareCastMethod(DependenceLine) + DeclareCastMethod(DependenceDistance) + DeclareCastMethod(DependencePoint) + DeclareCastMethod(DependenceNone) + DeclareCastMethod(DependenceEmpty) #undef DeclareCastMethod protected: const Loop* loop_; }; +// clang-format on class DependenceLine : public Constraint { public: diff --git a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp index a12b2ca3d..044633166 100644 --- a/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/merge_return_pass.cpp @@ -36,7 +36,13 @@ Pass::Status MergeReturnPass::Process() { ProcessFunction pfn = [&failed, is_shader, this](Function* function) { std::vector return_blocks = CollectReturnBlocks(function); if (return_blocks.size() <= 1) { - return false; + if (!is_shader || return_blocks.size() == 0) { + return false; + } + if (context()->GetStructuredCFGAnalysis()->ContainingConstruct( + return_blocks[0]->id()) == 0) { + return false; + } } function_ = function; diff --git a/3rdparty/spirv-tools/source/opt/optimizer.cpp b/3rdparty/spirv-tools/source/opt/optimizer.cpp index 62d886a64..6206f6468 100644 --- a/3rdparty/spirv-tools/source/opt/optimizer.cpp +++ b/3rdparty/spirv-tools/source/opt/optimizer.cpp @@ -528,6 +528,8 @@ bool Optimizer::Run(const uint32_t* original_binary, if (context == nullptr) return false; context->set_max_id_bound(opt_options->max_id_bound_); + context->set_preserve_bindings(opt_options->preserve_bindings_); + context->set_preserve_spec_constants(opt_options->preserve_spec_constants_); impl_->pass_manager.SetValidatorOptions(&opt_options->val_options_); impl_->pass_manager.SetTargetEnv(impl_->target_env); diff --git a/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h b/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h index 450522ec3..b0e3fefd6 100644 --- a/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h +++ b/3rdparty/spirv-tools/source/opt/scalar_analysis_nodes.h @@ -171,16 +171,17 @@ class SENode { bool IsCantCompute() const { return GetType() == CanNotCompute; } // Implements a casting method for each type. +// clang-format off #define DeclareCastMethod(target) \ virtual target* As##target() { return nullptr; } \ virtual const target* As##target() const { return nullptr; } - DeclareCastMethod(SEConstantNode); - DeclareCastMethod(SERecurrentNode); - DeclareCastMethod(SEAddNode); - DeclareCastMethod(SEMultiplyNode); - DeclareCastMethod(SENegative); - DeclareCastMethod(SEValueUnknown); - DeclareCastMethod(SECantCompute); + DeclareCastMethod(SEConstantNode) + DeclareCastMethod(SERecurrentNode) + DeclareCastMethod(SEAddNode) + DeclareCastMethod(SEMultiplyNode) + DeclareCastMethod(SENegative) + DeclareCastMethod(SEValueUnknown) + DeclareCastMethod(SECantCompute) #undef DeclareCastMethod // Get the analysis which has this node in its cache. @@ -200,6 +201,7 @@ class SENode { // The number of nodes created. static uint32_t NumberOfNodes; }; +// clang-format on // Function object to handle the hashing of SENodes. Hashing algorithm hashes // the type (as a string), the literal value of any constants, and the child diff --git a/3rdparty/spirv-tools/source/opt/simplification_pass.cpp b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp index 5fbafbdd1..6ea4566d5 100644 --- a/3rdparty/spirv-tools/source/opt/simplification_pass.cpp +++ b/3rdparty/spirv-tools/source/opt/simplification_pass.cpp @@ -55,8 +55,12 @@ bool SimplificationPass::SimplifyFunction(Function* function) { process_phis.insert(inst); } - if (inst->opcode() == SpvOpCopyObject || - folder.FoldInstruction(inst)) { + bool is_foldable_copy = + inst->opcode() == SpvOpCopyObject && + context()->get_decoration_mgr()->HaveSubsetOfDecorations( + inst->result_id(), inst->GetSingleWordInOperand(0)); + + if (is_foldable_copy || folder.FoldInstruction(inst)) { modified = true; context()->AnalyzeUses(inst); get_def_use_mgr()->ForEachUser(inst, [&work_list, &process_phis, @@ -85,7 +89,13 @@ bool SimplificationPass::SimplifyFunction(Function* function) { for (size_t i = 0; i < work_list.size(); ++i) { Instruction* inst = work_list[i]; in_work_list.erase(inst); - if (inst->opcode() == SpvOpCopyObject || folder.FoldInstruction(inst)) { + + bool is_foldable_copy = + inst->opcode() == SpvOpCopyObject && + context()->get_decoration_mgr()->HaveSubsetOfDecorations( + inst->result_id(), inst->GetSingleWordInOperand(0)); + + if (is_foldable_copy || folder.FoldInstruction(inst)) { modified = true; context()->AnalyzeUses(inst); get_def_use_mgr()->ForEachUser( diff --git a/3rdparty/spirv-tools/source/opt/types.cpp b/3rdparty/spirv-tools/source/opt/types.cpp index e345b2dcb..3717fd1b4 100644 --- a/3rdparty/spirv-tools/source/opt/types.cpp +++ b/3rdparty/spirv-tools/source/opt/types.cpp @@ -102,7 +102,7 @@ std::unique_ptr Type::Clone() const { #define DeclareKindCase(kind) \ case k##kind: \ type = MakeUnique(*this->As##kind()); \ - break; + break DeclareKindCase(Void); DeclareKindCase(Bool); DeclareKindCase(Integer); @@ -146,7 +146,7 @@ bool Type::operator==(const Type& other) const { switch (kind_) { #define DeclareKindCase(kind) \ case k##kind: \ - return As##kind()->IsSame(&other); + return As##kind()->IsSame(&other) DeclareKindCase(Void); DeclareKindCase(Bool); DeclareKindCase(Integer); @@ -195,7 +195,7 @@ void Type::GetHashWords(std::vector* words, #define DeclareKindCase(type) \ case k##type: \ As##type()->GetExtraHashWords(words, seen); \ - break; + break DeclareKindCase(Void); DeclareKindCase(Bool); DeclareKindCase(Integer); diff --git a/3rdparty/spirv-tools/source/opt/types.h b/3rdparty/spirv-tools/source/opt/types.h index e9dcc7068..c997b1fce 100644 --- a/3rdparty/spirv-tools/source/opt/types.h +++ b/3rdparty/spirv-tools/source/opt/types.h @@ -145,37 +145,6 @@ class Type { // TODO(alanbaker): Update this if variable pointers become a core feature. bool IsUniqueType(bool allowVariablePointers = false) const; -// A bunch of methods for casting this type to a given type. Returns this if the -// cast can be done, nullptr otherwise. -#define DeclareCastMethod(target) \ - virtual target* As##target() { return nullptr; } \ - virtual const target* As##target() const { return nullptr; } - DeclareCastMethod(Void); - DeclareCastMethod(Bool); - DeclareCastMethod(Integer); - DeclareCastMethod(Float); - DeclareCastMethod(Vector); - DeclareCastMethod(Matrix); - DeclareCastMethod(Image); - DeclareCastMethod(Sampler); - DeclareCastMethod(SampledImage); - DeclareCastMethod(Array); - DeclareCastMethod(RuntimeArray); - DeclareCastMethod(Struct); - DeclareCastMethod(Opaque); - DeclareCastMethod(Pointer); - DeclareCastMethod(Function); - DeclareCastMethod(Event); - DeclareCastMethod(DeviceEvent); - DeclareCastMethod(ReserveId); - DeclareCastMethod(Queue); - DeclareCastMethod(Pipe); - DeclareCastMethod(ForwardPointer); - DeclareCastMethod(PipeStorage); - DeclareCastMethod(NamedBarrier); - DeclareCastMethod(AccelerationStructureNV); -#undef DeclareCastMethod - bool operator==(const Type& other) const; // Returns the hash value of this type. @@ -197,6 +166,38 @@ class Type { std::vector* words, std::unordered_set* pSet) const = 0; +// A bunch of methods for casting this type to a given type. Returns this if the +// cast can be done, nullptr otherwise. +// clang-format off +#define DeclareCastMethod(target) \ + virtual target* As##target() { return nullptr; } \ + virtual const target* As##target() const { return nullptr; } + DeclareCastMethod(Void) + DeclareCastMethod(Bool) + DeclareCastMethod(Integer) + DeclareCastMethod(Float) + DeclareCastMethod(Vector) + DeclareCastMethod(Matrix) + DeclareCastMethod(Image) + DeclareCastMethod(Sampler) + DeclareCastMethod(SampledImage) + DeclareCastMethod(Array) + DeclareCastMethod(RuntimeArray) + DeclareCastMethod(Struct) + DeclareCastMethod(Opaque) + DeclareCastMethod(Pointer) + DeclareCastMethod(Function) + DeclareCastMethod(Event) + DeclareCastMethod(DeviceEvent) + DeclareCastMethod(ReserveId) + DeclareCastMethod(Queue) + DeclareCastMethod(Pipe) + DeclareCastMethod(ForwardPointer) + DeclareCastMethod(PipeStorage) + DeclareCastMethod(NamedBarrier) + DeclareCastMethod(AccelerationStructureNV) +#undef DeclareCastMethod + protected: // Decorations attached to this type. Each decoration is encoded as a vector // of uint32_t numbers. The first uint32_t number is the decoration value, @@ -210,6 +211,7 @@ class Type { Kind kind_; }; +// clang-format on class Integer : public Type { public: diff --git a/3rdparty/spirv-tools/source/opt/value_number_table.cpp b/3rdparty/spirv-tools/source/opt/value_number_table.cpp index 1bac63fab..8df34ef5a 100644 --- a/3rdparty/spirv-tools/source/opt/value_number_table.cpp +++ b/3rdparty/spirv-tools/source/opt/value_number_table.cpp @@ -78,8 +78,12 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) { return value; } + analysis::DecorationManager* dec_mgr = context()->get_decoration_mgr(); + // When we copy an object, the value numbers should be the same. - if (inst->opcode() == SpvOpCopyObject) { + if (inst->opcode() == SpvOpCopyObject && + dec_mgr->HaveTheSameDecorations(inst->result_id(), + inst->GetSingleWordInOperand(0))) { value = GetValueNumber(inst->GetSingleWordInOperand(0)); if (value != 0) { id_to_value_[inst->result_id()] = value; @@ -89,7 +93,9 @@ uint32_t ValueNumberTable::AssignValueNumber(Instruction* inst) { // Phi nodes are a type of copy. If all of the inputs have the same value // number, then we can assign the result of the phi the same value number. - if (inst->opcode() == SpvOpPhi) { + if (inst->opcode() == SpvOpPhi && + dec_mgr->HaveTheSameDecorations(inst->result_id(), + inst->GetSingleWordInOperand(0))) { value = GetValueNumber(inst->GetSingleWordInOperand(0)); if (value != 0) { for (uint32_t op = 2; op < inst->NumInOperands(); op += 2) { diff --git a/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp b/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp index f6319ca31..9a2cb9fda 100644 --- a/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp +++ b/3rdparty/spirv-tools/source/spirv_fuzzer_options.cpp @@ -14,7 +14,15 @@ #include "source/spirv_fuzzer_options.h" -spv_fuzzer_options_t::spv_fuzzer_options_t() = default; +namespace { +// The default maximum number of steps for the reducer to run before giving up. +const uint32_t kDefaultStepLimit = 250; +} // namespace + +spv_fuzzer_options_t::spv_fuzzer_options_t() + : has_random_seed(false), + random_seed(0), + shrinker_step_limit(kDefaultStepLimit) {} SPIRV_TOOLS_EXPORT spv_fuzzer_options spvFuzzerOptionsCreate() { return new spv_fuzzer_options_t(); @@ -29,3 +37,8 @@ SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetRandomSeed( options->has_random_seed = true; options->random_seed = seed; } + +SPIRV_TOOLS_EXPORT void spvFuzzerOptionsSetShrinkerStepLimit( + spv_fuzzer_options options, uint32_t shrinker_step_limit) { + options->shrinker_step_limit = shrinker_step_limit; +} diff --git a/3rdparty/spirv-tools/source/spirv_fuzzer_options.h b/3rdparty/spirv-tools/source/spirv_fuzzer_options.h index 1193bfd72..3b8e15ca1 100644 --- a/3rdparty/spirv-tools/source/spirv_fuzzer_options.h +++ b/3rdparty/spirv-tools/source/spirv_fuzzer_options.h @@ -26,8 +26,11 @@ struct spv_fuzzer_options_t { spv_fuzzer_options_t(); // See spvFuzzerOptionsSetRandomSeed. - bool has_random_seed = false; - uint32_t random_seed = 0; + bool has_random_seed; + uint32_t random_seed; + + // See spvFuzzerOptionsSetShrinkerStepLimit. + uint32_t shrinker_step_limit; }; #endif // SOURCE_SPIRV_FUZZER_OPTIONS_H_ diff --git a/3rdparty/spirv-tools/source/spirv_optimizer_options.cpp b/3rdparty/spirv-tools/source/spirv_optimizer_options.cpp index 30db4e2de..e92ffc0f4 100644 --- a/3rdparty/spirv-tools/source/spirv_optimizer_options.cpp +++ b/3rdparty/spirv-tools/source/spirv_optimizer_options.cpp @@ -39,3 +39,13 @@ SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetMaxIdBound( spv_optimizer_options options, uint32_t val) { options->max_id_bound_ = val; } + +SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveBindings( + spv_optimizer_options options, bool val) { + options->preserve_bindings_ = val; +} + +SPIRV_TOOLS_EXPORT void spvOptimizerOptionsSetPreserveSpecConstants( + spv_optimizer_options options, bool val) { + options->preserve_spec_constants_ = val; +} diff --git a/3rdparty/spirv-tools/source/spirv_optimizer_options.h b/3rdparty/spirv-tools/source/spirv_optimizer_options.h index 1eb4d3f1b..aa76d20b1 100644 --- a/3rdparty/spirv-tools/source/spirv_optimizer_options.h +++ b/3rdparty/spirv-tools/source/spirv_optimizer_options.h @@ -24,7 +24,9 @@ struct spv_optimizer_options_t { spv_optimizer_options_t() : run_validator_(true), val_options_(), - max_id_bound_(kDefaultMaxIdBound) {} + max_id_bound_(kDefaultMaxIdBound), + preserve_bindings_(false), + preserve_spec_constants_(false) {} // When true the validator will be run before optimizations are run. bool run_validator_; @@ -36,5 +38,12 @@ struct spv_optimizer_options_t { // this value must be at least 0x3FFFFF, but implementations can allow for a // higher value. uint32_t max_id_bound_; + + // When true, all binding declarations within the module should be preserved. + bool preserve_bindings_; + + // When true, all specialization constants within the module should be + // preserved. + bool preserve_spec_constants_; }; #endif // SOURCE_SPIRV_OPTIMIZER_OPTIONS_H_ diff --git a/3rdparty/spirv-tools/source/spirv_reducer_options.cpp b/3rdparty/spirv-tools/source/spirv_reducer_options.cpp index 0426800e1..5801d0a16 100644 --- a/3rdparty/spirv-tools/source/spirv_reducer_options.cpp +++ b/3rdparty/spirv-tools/source/spirv_reducer_options.cpp @@ -17,6 +17,14 @@ #include "source/spirv_reducer_options.h" +namespace { +// The default maximum number of steps the reducer will take before giving up. +const uint32_t kDefaultStepLimit = 250; +} // namespace + +spv_reducer_options_t::spv_reducer_options_t() + : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {} + SPIRV_TOOLS_EXPORT spv_reducer_options spvReducerOptionsCreate() { return new spv_reducer_options_t(); } diff --git a/3rdparty/spirv-tools/source/spirv_reducer_options.h b/3rdparty/spirv-tools/source/spirv_reducer_options.h index 363517bb5..1a431cce2 100644 --- a/3rdparty/spirv-tools/source/spirv_reducer_options.h +++ b/3rdparty/spirv-tools/source/spirv_reducer_options.h @@ -20,14 +20,10 @@ #include #include -// The default maximum number of steps for the reducer to run before giving up. -const uint32_t kDefaultStepLimit = 250; - // Manages command line options passed to the SPIR-V Reducer. New struct // members may be added for any new option. struct spv_reducer_options_t { - spv_reducer_options_t() - : step_limit(kDefaultStepLimit), fail_on_validation_error(false) {} + spv_reducer_options_t(); // See spvReducerOptionsSetStepLimit. uint32_t step_limit; diff --git a/3rdparty/spirv-tools/source/val/validate.cpp b/3rdparty/spirv-tools/source/val/validate.cpp index 0840662f4..6f1a26c6e 100644 --- a/3rdparty/spirv-tools/source/val/validate.cpp +++ b/3rdparty/spirv-tools/source/val/validate.cpp @@ -412,6 +412,7 @@ spv_result_t ValidateBinaryUsingContextAndValidationState( // those checks register the limitation checked here. for (const auto inst : vstate->ordered_instructions()) { if (auto error = ValidateExecutionLimitations(*vstate, &inst)) return error; + if (auto error = ValidateSmallTypeUses(*vstate, &inst)) return error; } return SPV_SUCCESS; diff --git a/3rdparty/spirv-tools/source/val/validate.h b/3rdparty/spirv-tools/source/val/validate.h index aaae57014..b6c4072ee 100644 --- a/3rdparty/spirv-tools/source/val/validate.h +++ b/3rdparty/spirv-tools/source/val/validate.h @@ -208,6 +208,13 @@ spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst); spv_result_t ValidateExecutionLimitations(ValidationState_t& _, const Instruction* inst); +/// Validates restricted uses of 8- and 16-bit types. +/// +/// Validates shaders that uses 8- or 16-bit storage capabilities, but not full +/// capabilities only have appropriate uses of those types. +spv_result_t ValidateSmallTypeUses(ValidationState_t& _, + const Instruction* inst); + /// @brief Validate the ID's within a SPIR-V binary /// /// @param[in] pInstructions array of instructions diff --git a/3rdparty/spirv-tools/source/val/validate_composites.cpp b/3rdparty/spirv-tools/source/val/validate_composites.cpp index 1d93378c8..9b7f592bf 100644 --- a/3rdparty/spirv-tools/source/val/validate_composites.cpp +++ b/3rdparty/spirv-tools/source/val/validate_composites.cpp @@ -158,6 +158,12 @@ spv_result_t ValidateVectorExtractDynamic(ValidationState_t& _, return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Index to be int scalar"; } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot extract from a vector of 8- or 16-bit types"; + } return SPV_SUCCESS; } @@ -188,6 +194,12 @@ spv_result_t ValidateVectorInsertDyanmic(ValidationState_t& _, return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Index to be int scalar"; } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot insert into a vector of 8- or 16-bit types"; + } return SPV_SUCCESS; } @@ -344,6 +356,12 @@ spv_result_t ValidateCompositeConstruct(ValidationState_t& _, << "Expected Result Type to be a composite type"; } } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot create a composite containing 8- or 16-bit types"; + } return SPV_SUCCESS; } @@ -362,6 +380,12 @@ spv_result_t ValidateCompositeExtract(ValidationState_t& _, "the composite (Op" << spvOpcodeString(_.GetIdOpcode(member_type)) << ")."; } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot extract from a composite of 8- or 16-bit types"; + } return SPV_SUCCESS; } @@ -391,6 +415,12 @@ spv_result_t ValidateCompositeInsert(ValidationState_t& _, "Composite (Op" << spvOpcodeString(_.GetIdOpcode(member_type)) << ")."; } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot insert into a composite of 8- or 16-bit types"; + } return SPV_SUCCESS; } @@ -439,6 +469,12 @@ spv_result_t ValidateTranspose(ValidationState_t& _, const Instruction* inst) { << "Expected number of columns and the column size of Matrix " << "to be the reverse of those of Result Type"; } + + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot transpose matrices of 16-bit floats"; + } return SPV_SUCCESS; } @@ -510,6 +546,12 @@ spv_result_t ValidateVectorShuffle(ValidationState_t& _, } } + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot shuffle a vector of 8- or 16-bit types"; + } + return SPV_SUCCESS; } @@ -528,6 +570,12 @@ spv_result_t ValidateCopyLogical(ValidationState_t& _, << "Result Type does not logically match the Operand type"; } + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Cannot copy composites of 8- or 16-bit types"; + } + return SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validate_constants.cpp b/3rdparty/spirv-tools/source/val/validate_constants.cpp index 191344ed5..04544aa3a 100644 --- a/3rdparty/spirv-tools/source/val/validate_constants.cpp +++ b/3rdparty/spirv-tools/source/val/validate_constants.cpp @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "source/val/validate.h" - #include "source/opcode.h" #include "source/val/instruction.h" +#include "source/val/validate.h" #include "source/val/validation_state.h" namespace spvtools { @@ -432,6 +431,16 @@ spv_result_t ConstantPass(ValidationState_t& _, const Instruction* inst) { break; } + // Generally disallow creating 8- or 16-bit constants unless the full + // capabilities are present. + if (spvOpcodeIsConstant(inst->opcode()) && + _.HasCapability(SpvCapabilityShader) && + !_.IsPointerType(inst->type_id()) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot form constants of 8- or 16-bit types"; + } + return SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validate_conversion.cpp b/3rdparty/spirv-tools/source/val/validate_conversion.cpp index 17af9f465..f7eb88110 100644 --- a/3rdparty/spirv-tools/source/val/validate_conversion.cpp +++ b/3rdparty/spirv-tools/source/val/validate_conversion.cpp @@ -58,11 +58,6 @@ spv_result_t ConversionPass(ValidationState_t& _, const Instruction* inst) { << spvOpcodeString(opcode); } - if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type))) - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Invalid cast to 8-bit integer from a floating-point: " - << spvOpcodeString(opcode); - break; } @@ -93,11 +88,6 @@ spv_result_t ConversionPass(ValidationState_t& _, const Instruction* inst) { << spvOpcodeString(opcode); } - if (!_.features().use_int8_type && (8 == _.GetBitWidth(result_type))) - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Invalid cast to 8-bit integer from a floating-point: " - << spvOpcodeString(opcode); - break; } @@ -130,11 +120,6 @@ spv_result_t ConversionPass(ValidationState_t& _, const Instruction* inst) { << spvOpcodeString(opcode); } - if (!_.features().use_int8_type && (8 == _.GetBitWidth(input_type))) - return _.diag(SPV_ERROR_INVALID_DATA, inst) - << "Invalid cast to floating-point from an 8-bit integer: " - << spvOpcodeString(opcode); - break; } @@ -509,6 +494,25 @@ spv_result_t ConversionPass(ValidationState_t& _, const Instruction* inst) { break; } + if (_.HasCapability(SpvCapabilityShader)) { + switch (inst->opcode()) { + case SpvOpConvertFToU: + case SpvOpConvertFToS: + case SpvOpConvertSToF: + case SpvOpConvertUToF: + case SpvOpBitcast: + if (_.ContainsLimitedUseIntOrFloatType(inst->type_id()) || + _.ContainsLimitedUseIntOrFloatType(_.GetOperandTypeId(inst, 2u))) { + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "8- or 16-bit types can only be used with width-only " + "conversions"; + } + break; + default: + break; + } + } + return SPV_SUCCESS; } diff --git a/3rdparty/spirv-tools/source/val/validate_memory.cpp b/3rdparty/spirv-tools/source/val/validate_memory.cpp index 29fdd5dc5..f31c5a022 100644 --- a/3rdparty/spirv-tools/source/val/validate_memory.cpp +++ b/3rdparty/spirv-tools/source/val/validate_memory.cpp @@ -428,8 +428,7 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { } } - const auto storage_class = - inst->GetOperandAs(storage_class_index); + auto storage_class = inst->GetOperandAs(storage_class_index); if (storage_class != SpvStorageClassWorkgroup && storage_class != SpvStorageClassCrossWorkgroup && storage_class != SpvStorageClassPrivate && @@ -604,10 +603,10 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { } // Vulkan specific validation rules for OpTypeRuntimeArray + const auto type_index = 2; + const auto value_id = result_type->GetOperandAs(type_index); + auto value_type = _.FindDef(value_id); if (spvIsVulkanEnv(_.context()->target_env)) { - const auto type_index = 2; - const auto value_id = result_type->GetOperandAs(type_index); - auto value_type = _.FindDef(value_id); // OpTypeRuntimeArray should only ever be in a container like OpTypeStruct, // so should never appear as a bare variable. // Unless the module has the RuntimeDescriptorArrayEXT capability. @@ -665,9 +664,6 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { // WebGPU specific validation rules for OpTypeRuntimeArray if (spvIsWebGPUEnv(_.context()->target_env)) { - const auto type_index = 2; - const auto value_id = result_type->GetOperandAs(type_index); - auto value_type = _.FindDef(value_id); // OpTypeRuntimeArray should only ever be in an OpTypeStruct, // so should never appear as a bare variable. if (value_type && value_type->opcode() == SpvOpTypeRuntimeArray) { @@ -712,6 +708,119 @@ spv_result_t ValidateVariable(ValidationState_t& _, const Instruction* inst) { "parameters"; } + if (_.HasCapability(SpvCapabilityShader)) { + // Don't allow variables containing 16-bit elements without the appropriate + // capabilities. + if ((!_.HasCapability(SpvCapabilityInt16) && + _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 16)) || + (!_.HasCapability(SpvCapabilityFloat16) && + _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeFloat, 16))) { + auto underlying_type = value_type; + while (underlying_type->opcode() == SpvOpTypePointer) { + storage_class = underlying_type->GetOperandAs(1u); + underlying_type = + _.FindDef(underlying_type->GetOperandAs(2u)); + } + bool storage_class_ok = true; + std::string sc_name = _.grammar().lookupOperandName( + SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class); + switch (storage_class) { + case SpvStorageClassStorageBuffer: + case SpvStorageClassPhysicalStorageBufferEXT: + if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess)) { + storage_class_ok = false; + } + break; + case SpvStorageClassUniform: + if (!_.HasCapability( + SpvCapabilityUniformAndStorageBuffer16BitAccess)) { + if (underlying_type->opcode() == SpvOpTypeArray || + underlying_type->opcode() == SpvOpTypeRuntimeArray) { + underlying_type = + _.FindDef(underlying_type->GetOperandAs(1u)); + } + if (!_.HasCapability(SpvCapabilityStorageBuffer16BitAccess) || + !_.HasDecoration(underlying_type->id(), + SpvDecorationBufferBlock)) { + storage_class_ok = false; + } + } + break; + case SpvStorageClassPushConstant: + if (!_.HasCapability(SpvCapabilityStoragePushConstant16)) { + storage_class_ok = false; + } + break; + case SpvStorageClassInput: + case SpvStorageClassOutput: + if (!_.HasCapability(SpvCapabilityStorageInputOutput16)) { + storage_class_ok = false; + } + break; + default: + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot allocate a variable containing a 16-bit type in " + << sc_name << " storage class"; + } + if (!storage_class_ok) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Allocating a variable containing a 16-bit element in " + << sc_name << " storage class requires an additional capability"; + } + } + // Don't allow variables containing 8-bit elements without the appropriate + // capabilities. + if (!_.HasCapability(SpvCapabilityInt8) && + _.ContainsSizedIntOrFloatType(value_id, SpvOpTypeInt, 8)) { + auto underlying_type = value_type; + while (underlying_type->opcode() == SpvOpTypePointer) { + storage_class = underlying_type->GetOperandAs(1u); + underlying_type = + _.FindDef(underlying_type->GetOperandAs(2u)); + } + bool storage_class_ok = true; + std::string sc_name = _.grammar().lookupOperandName( + SPV_OPERAND_TYPE_STORAGE_CLASS, storage_class); + switch (storage_class) { + case SpvStorageClassStorageBuffer: + case SpvStorageClassPhysicalStorageBufferEXT: + if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess)) { + storage_class_ok = false; + } + break; + case SpvStorageClassUniform: + if (!_.HasCapability( + SpvCapabilityUniformAndStorageBuffer8BitAccess)) { + if (underlying_type->opcode() == SpvOpTypeArray || + underlying_type->opcode() == SpvOpTypeRuntimeArray) { + underlying_type = + _.FindDef(underlying_type->GetOperandAs(1u)); + } + if (!_.HasCapability(SpvCapabilityStorageBuffer8BitAccess) || + !_.HasDecoration(underlying_type->id(), + SpvDecorationBufferBlock)) { + storage_class_ok = false; + } + } + break; + case SpvStorageClassPushConstant: + if (!_.HasCapability(SpvCapabilityStoragePushConstant8)) { + storage_class_ok = false; + } + break; + default: + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot allocate a variable containing a 8-bit type in " + << sc_name << " storage class"; + } + if (!storage_class_ok) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Allocating a variable containing a 8-bit element in " + << sc_name << " storage class requires an additional capability"; + } + } + } + return SPV_SUCCESS; } @@ -757,6 +866,18 @@ spv_result_t ValidateLoad(ValidationState_t& _, const Instruction* inst) { if (auto error = CheckMemoryAccess(_, inst, 3)) return error; + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id()) && + result_type->opcode() != SpvOpTypePointer) { + if (result_type->opcode() != SpvOpTypeInt && + result_type->opcode() != SpvOpTypeFloat && + result_type->opcode() != SpvOpTypeVector && + result_type->opcode() != SpvOpTypeMatrix) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "8- or 16-bit loads must be a scalar, vector or matrix type"; + } + } + return SPV_SUCCESS; } @@ -864,6 +985,18 @@ spv_result_t ValidateStore(ValidationState_t& _, const Instruction* inst) { if (auto error = CheckMemoryAccess(_, inst, 2)) return error; + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id()) && + object_type->opcode() != SpvOpTypePointer) { + if (object_type->opcode() != SpvOpTypeInt && + object_type->opcode() != SpvOpTypeFloat && + object_type->opcode() != SpvOpTypeVector && + object_type->opcode() != SpvOpTypeMatrix) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "8- or 16-bit stores must be a scalar, vector or matrix type"; + } + } + return SPV_SUCCESS; } @@ -1017,7 +1150,20 @@ spv_result_t ValidateCopyMemory(ValidationState_t& _, const Instruction* inst) { if (auto error = CheckMemoryAccess(_, inst, 3)) return error; } - return ValidateCopyMemoryMemoryAccess(_, inst); + if (auto error = ValidateCopyMemoryMemoryAccess(_, inst)) return error; + + // Get past the pointers to avoid checking a pointer copy. + auto sub_type = _.FindDef(target_pointer_type->GetOperandAs(2)); + while (sub_type->opcode() == SpvOpTypePointer) { + sub_type = _.FindDef(sub_type->GetOperandAs(2)); + } + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(sub_type->id())) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot copy memory of objects containing 8- or 16-bit types"; + } + + return SPV_SUCCESS; } spv_result_t ValidateAccessChain(ValidationState_t& _, diff --git a/3rdparty/spirv-tools/source/val/validate_misc.cpp b/3rdparty/spirv-tools/source/val/validate_misc.cpp index dcc65f37f..28e3fc684 100644 --- a/3rdparty/spirv-tools/source/val/validate_misc.cpp +++ b/3rdparty/spirv-tools/source/val/validate_misc.cpp @@ -22,8 +22,29 @@ namespace spvtools { namespace val { +namespace { + +spv_result_t ValidateUndef(ValidationState_t& _, const Instruction* inst) { + if (_.HasCapability(SpvCapabilityShader) && + _.ContainsLimitedUseIntOrFloatType(inst->type_id()) && + !_.IsPointerType(inst->type_id())) { + return _.diag(SPV_ERROR_INVALID_ID, inst) + << "Cannot create undefined values with 8- or 16-bit types"; + } + + return SPV_SUCCESS; +} + +} // namespace spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) { + switch (inst->opcode()) { + case SpvOpUndef: + if (auto error = ValidateUndef(_, inst)) return error; + break; + default: + break; + } switch (inst->opcode()) { case SpvOpBeginInvocationInterlockEXT: case SpvOpEndInvocationInterlockEXT: @@ -70,6 +91,25 @@ spv_result_t MiscPass(ValidationState_t& _, const Instruction* inst) { return true; }); break; + case SpvOpDemoteToHelperInvocationEXT: + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + "OpDemoteToHelperInvocationEXT requires Fragment execution " + "model"); + break; + case SpvOpIsHelperInvocationEXT: { + const uint32_t result_type = inst->type_id(); + _.function(inst->function()->id()) + ->RegisterExecutionModelLimitation( + SpvExecutionModelFragment, + "OpIsHelperInvocationEXT requires Fragment execution model"); + if (!_.IsBoolScalarType(result_type)) + return _.diag(SPV_ERROR_INVALID_DATA, inst) + << "Expected bool scalar type as Result Type: " + << spvOpcodeString(inst->opcode()); + break; + } default: break; } diff --git a/3rdparty/spirv-tools/source/val/validate_scopes.cpp b/3rdparty/spirv-tools/source/val/validate_scopes.cpp index 7e396c046..c607984e4 100644 --- a/3rdparty/spirv-tools/source/val/validate_scopes.cpp +++ b/3rdparty/spirv-tools/source/val/validate_scopes.cpp @@ -123,11 +123,11 @@ 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 && value != SpvScopeSubgroup) { + if (value != SpvScopeWorkgroup) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << spvOpcodeString(opcode) << ": in WebGPU environment Execution Scope is limited to " - << "Workgroup and Subgroup"; + << "Workgroup"; } } @@ -229,12 +229,12 @@ spv_result_t ValidateMemoryScope(ValidationState_t& _, const Instruction* inst, // WebGPU specific rules if (spvIsWebGPUEnv(_.context()->target_env)) { - if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup && + 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, Subgroup and QueuFamilyKHR"; + << "Workgroup, Invocation, and QueueFamilyKHR"; } } diff --git a/3rdparty/spirv-tools/source/val/validate_small_type_uses.cpp b/3rdparty/spirv-tools/source/val/validate_small_type_uses.cpp new file mode 100644 index 000000000..9db82e7c7 --- /dev/null +++ b/3rdparty/spirv-tools/source/val/validate_small_type_uses.cpp @@ -0,0 +1,57 @@ +// 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/val/validate.h" + +#include "source/val/instruction.h" +#include "source/val/validation_state.h" + +namespace spvtools { +namespace val { + +spv_result_t ValidateSmallTypeUses(ValidationState_t& _, + const Instruction* inst) { + if (!_.HasCapability(SpvCapabilityShader) || inst->type_id() == 0 || + !_.ContainsLimitedUseIntOrFloatType(inst->type_id())) { + return SPV_SUCCESS; + } + + if (_.IsPointerType(inst->type_id())) return SPV_SUCCESS; + + // The validator should previously have checked ways to generate 8- or 16-bit + // types. So we only need to considervalid paths from source to sink. + // When restricted, uses of 8- or 16-bit types can only be stores, + // width-only conversions, decorations and copy object. + for (auto use : inst->uses()) { + const auto* user = use.first; + switch (user->opcode()) { + case SpvOpDecorate: + case SpvOpDecorateId: + case SpvOpCopyObject: + case SpvOpStore: + case SpvOpFConvert: + case SpvOpUConvert: + case SpvOpSConvert: + break; + default: + return _.diag(SPV_ERROR_INVALID_ID, user) + << "Invalid use of 8- or 16-bit result"; + } + } + + return SPV_SUCCESS; +} + +} // namespace val +} // namespace spvtools diff --git a/3rdparty/spirv-tools/source/val/validation_state.cpp b/3rdparty/spirv-tools/source/val/validation_state.cpp index c5673eddd..794d0f7b8 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.cpp +++ b/3rdparty/spirv-tools/source/val/validation_state.cpp @@ -1226,5 +1226,56 @@ const Instruction* ValidationState_t::TracePointer( return base_ptr; } +bool ValidationState_t::ContainsSizedIntOrFloatType(uint32_t id, SpvOp type, + uint32_t width) const { + if (type != SpvOpTypeInt && type != SpvOpTypeFloat) return false; + + const auto inst = FindDef(id); + if (!inst) return false; + + if (inst->opcode() == type) { + return inst->GetOperandAs(1u) == width; + } + + switch (inst->opcode()) { + case SpvOpTypeArray: + case SpvOpTypeRuntimeArray: + case SpvOpTypeVector: + case SpvOpTypeMatrix: + case SpvOpTypeImage: + case SpvOpTypeSampledImage: + case SpvOpTypeCooperativeMatrixNV: + return ContainsSizedIntOrFloatType(inst->GetOperandAs(1u), type, + width); + case SpvOpTypePointer: + if (IsForwardPointer(id)) return false; + return ContainsSizedIntOrFloatType(inst->GetOperandAs(2u), type, + width); + case SpvOpTypeFunction: + case SpvOpTypeStruct: { + for (uint32_t i = 1; i < inst->operands().size(); ++i) { + if (ContainsSizedIntOrFloatType(inst->GetOperandAs(i), type, + width)) + return true; + } + return false; + } + default: + return false; + } +} + +bool ValidationState_t::ContainsLimitedUseIntOrFloatType(uint32_t id) const { + if ((!HasCapability(SpvCapabilityInt16) && + ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 16)) || + (!HasCapability(SpvCapabilityInt8) && + ContainsSizedIntOrFloatType(id, SpvOpTypeInt, 8)) || + (!HasCapability(SpvCapabilityFloat16) && + ContainsSizedIntOrFloatType(id, SpvOpTypeFloat, 16))) { + return true; + } + return false; +} + } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/source/val/validation_state.h b/3rdparty/spirv-tools/source/val/validation_state.h index a0914eca8..ad16bcbdd 100644 --- a/3rdparty/spirv-tools/source/val/validation_state.h +++ b/3rdparty/spirv-tools/source/val/validation_state.h @@ -569,6 +569,14 @@ class ValidationState_t { bool IsIntCooperativeMatrixType(uint32_t id) const; bool IsUnsignedIntCooperativeMatrixType(uint32_t id) const; + // Returns true if |id| is a type id that contains |type| (or integer or + // floating point type) of |width| bits. + bool ContainsSizedIntOrFloatType(uint32_t id, SpvOp type, + uint32_t width) const; + // Returns true if |id| is a type id that contains a 8- or 16-bit int or + // 16-bit float that is not generally enabled for use. + bool ContainsLimitedUseIntOrFloatType(uint32_t id) const; + // Gets value from OpConstant and OpSpecConstant as uint64. // Returns false on failure (no instruction, wrong instruction, not int). bool GetConstantValUint64(uint32_t id, uint64_t* val) const; diff --git a/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt b/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt index 7b47b5530..9e4066b17 100644 --- a/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/fuzz/CMakeLists.txt @@ -18,6 +18,7 @@ if (${SPIRV_BUILD_FUZZER}) fuzz_test_util.h fuzzer_replayer_test.cpp + fuzzer_shrinker_test.cpp fact_manager_test.cpp fuzz_test_util.cpp fuzzer_pass_add_useful_constructs_test.cpp diff --git a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h index 202622c85..76b7fc292 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h +++ b/3rdparty/spirv-tools/test/fuzz/fuzz_test_util.h @@ -59,6 +59,11 @@ const uint32_t kFuzzAssembleOption = const uint32_t kFuzzDisassembleOption = SPV_BINARY_TO_TEXT_OPTION_NO_HEADER | SPV_BINARY_TO_TEXT_OPTION_INDENT; +// A silent message consumer. +const spvtools::MessageConsumer kSilentConsumer = + [](spv_message_level_t, const char*, const spv_position_t&, + const char*) -> void {}; + } // 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 8b3775b55..2cb595a67 100644 --- a/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp +++ b/3rdparty/spirv-tools/test/fuzz/fuzzer_replayer_test.cpp @@ -43,9 +43,10 @@ void RunFuzzerAndReplayer(const std::string& shader, spvFuzzerOptionsSetRandomSeed(fuzzer_options, seed); Fuzzer fuzzer(env); + fuzzer.SetMessageConsumer(kSilentConsumer); auto fuzzer_result_status = - fuzzer.Run(binary_in, initial_facts, &fuzzer_binary_out, - &fuzzer_transformation_sequence_out, fuzzer_options); + 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)); @@ -53,6 +54,7 @@ void RunFuzzerAndReplayer(const std::string& shader, protobufs::TransformationSequence replayer_transformation_sequence_out; Replayer replayer(env); + replayer.SetMessageConsumer(kSilentConsumer); auto replayer_result_status = replayer.Run( binary_in, initial_facts, fuzzer_transformation_sequence_out, &replayer_binary_out, &replayer_transformation_sequence_out); diff --git a/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp b/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp new file mode 100644 index 000000000..644ace4f0 --- /dev/null +++ b/3rdparty/spirv-tools/test/fuzz/fuzzer_shrinker_test.cpp @@ -0,0 +1,1109 @@ +// 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 "source/fuzz/fuzzer.h" +#include "source/fuzz/pseudo_random_generator.h" +#include "source/fuzz/shrinker.h" +#include "source/fuzz/uniform_buffer_element_descriptor.h" +#include "test/fuzz/fuzz_test_util.h" + +namespace spvtools { +namespace fuzz { +namespace { + +// Abstract class exposing an interestingness function as a virtual method. +class InterestingnessTest { + public: + virtual ~InterestingnessTest() = default; + + // Abstract method that subclasses should implement for specific notions of + // interestingness. Its signature matches Shrinker::InterestingnessFunction. + // Argument |binary| is the SPIR-V binary to be checked; |counter| is used for + // debugging purposes. + virtual bool Interesting(const std::vector& binary, + uint32_t counter) = 0; + + // Yields the Interesting instance method wrapped in a function object. + Shrinker::InterestingnessFunction AsFunction() { + return std::bind(&InterestingnessTest::Interesting, this, + std::placeholders::_1, std::placeholders::_2); + } +}; + +// A test that says all binaries are interesting. +class AlwaysInteresting : public InterestingnessTest { + public: + bool Interesting(const std::vector&, uint32_t) override { + return true; + } +}; + +// A test that says a binary is interesting first time round, and uninteresting +// thereafter. +class OnlyInterestingFirstTime : public InterestingnessTest { + public: + explicit OnlyInterestingFirstTime() : first_time_(true) {} + + bool Interesting(const std::vector&, uint32_t) override { + if (first_time_) { + first_time_ = false; + return true; + } + return false; + } + + private: + bool first_time_; +}; + +// A test that says a binary is interesting first time round, after which +// interestingness ping pongs between false and true. +class PingPong : public InterestingnessTest { + public: + explicit PingPong() : interesting_(false) {} + + bool Interesting(const std::vector&, uint32_t) override { + interesting_ = !interesting_; + return interesting_; + } + + private: + bool interesting_; +}; + +// A test that says a binary is interesting first time round, thereafter +// decides at random whether it is interesting. This allows the logic of the +// shrinker to be exercised quite a bit. +class InterestingThenRandom : public InterestingnessTest { + public: + InterestingThenRandom(const PseudoRandomGenerator& random_generator) + : first_time_(true), random_generator_(random_generator) {} + + bool Interesting(const std::vector&, uint32_t) override { + if (first_time_) { + first_time_ = false; + return true; + } + return random_generator_.RandomBool(); + } + + private: + bool first_time_; + PseudoRandomGenerator random_generator_; +}; + +// |binary_in| and |initial_facts| are a SPIR-V binary and sequence of facts to +// which |transformation_sequence_in| can be applied. Shrinking of +// |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|. +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; + + // Run the shrinker. + Shrinker shrinker(target_env, kStepLimit); + shrinker.SetMessageConsumer(kSilentConsumer); + + std::vector binary_out; + protobufs::TransformationSequence transformations_out; + Shrinker::ShrinkerResultStatus shrinker_result_status = + shrinker.Run(binary_in, initial_facts, transformation_sequence_in, + interestingness_function, &binary_out, &transformations_out); + ASSERT_TRUE(Shrinker::ShrinkerResultStatus::kComplete == + shrinker_result_status || + Shrinker::ShrinkerResultStatus::kStepLimitReached == + shrinker_result_status); + + // If a non-empty expected binary was provided, check that it matches the + // result of shrinking and that the expected number of transformations remain. + if (!expected_binary_out.empty()) { + ASSERT_EQ(expected_binary_out, binary_out); + ASSERT_EQ(expected_transformations_out_size, + static_cast(transformations_out.transformation_size())); + } +} + +// 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 +// - 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) { + const auto env = SPV_ENV_UNIVERSAL_1_3; + + std::vector binary_in; + SpirvTools t(env); + 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)); + + // 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); + + // 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())); + + // 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); + + // 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); + } +} + +TEST(FuzzerShrinkerTest, Miscellaneous1) { + // The following SPIR-V came from this GLSL: + // + // #version 310 es + // + // void foo() { + // int x; + // x = 2; + // for (int i = 0; i < 100; i++) { + // x += i; + // x = x * 2; + // } + // return; + // } + // + // void main() { + // foo(); + // for (int i = 0; i < 10; i++) { + // int j = 20; + // while(j > 0) { + // foo(); + // j--; + // } + // do { + // i++; + // } while(i < 4); + // } + // } + + const 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 %6 "foo(" + OpName %10 "x" + OpName %12 "i" + OpName %33 "i" + OpName %42 "j" + OpDecorate %10 RelaxedPrecision + OpDecorate %12 RelaxedPrecision + OpDecorate %19 RelaxedPrecision + OpDecorate %23 RelaxedPrecision + OpDecorate %24 RelaxedPrecision + OpDecorate %25 RelaxedPrecision + OpDecorate %26 RelaxedPrecision + OpDecorate %27 RelaxedPrecision + OpDecorate %28 RelaxedPrecision + OpDecorate %30 RelaxedPrecision + OpDecorate %33 RelaxedPrecision + OpDecorate %39 RelaxedPrecision + OpDecorate %42 RelaxedPrecision + OpDecorate %49 RelaxedPrecision + OpDecorate %52 RelaxedPrecision + OpDecorate %53 RelaxedPrecision + OpDecorate %58 RelaxedPrecision + OpDecorate %59 RelaxedPrecision + OpDecorate %60 RelaxedPrecision + OpDecorate %63 RelaxedPrecision + OpDecorate %64 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %8 = OpTypeInt 32 1 + %9 = OpTypePointer Function %8 + %11 = OpConstant %8 2 + %13 = OpConstant %8 0 + %20 = OpConstant %8 100 + %21 = OpTypeBool + %29 = OpConstant %8 1 + %40 = OpConstant %8 10 + %43 = OpConstant %8 20 + %61 = OpConstant %8 4 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %33 = OpVariable %9 Function + %42 = OpVariable %9 Function + %32 = OpFunctionCall %2 %6 + OpStore %33 %13 + OpBranch %34 + %34 = OpLabel + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %39 = OpLoad %8 %33 + %41 = OpSLessThan %21 %39 %40 + OpBranchConditional %41 %35 %36 + %35 = OpLabel + OpStore %42 %43 + OpBranch %44 + %44 = OpLabel + OpLoopMerge %46 %47 None + OpBranch %48 + %48 = OpLabel + %49 = OpLoad %8 %42 + %50 = OpSGreaterThan %21 %49 %13 + OpBranchConditional %50 %45 %46 + %45 = OpLabel + %51 = OpFunctionCall %2 %6 + %52 = OpLoad %8 %42 + %53 = OpISub %8 %52 %29 + OpStore %42 %53 + OpBranch %47 + %47 = OpLabel + OpBranch %44 + %46 = OpLabel + OpBranch %54 + %54 = OpLabel + OpLoopMerge %56 %57 None + OpBranch %55 + %55 = OpLabel + %58 = OpLoad %8 %33 + %59 = OpIAdd %8 %58 %29 + OpStore %33 %59 + OpBranch %57 + %57 = OpLabel + %60 = OpLoad %8 %33 + %62 = OpSLessThan %21 %60 %61 + OpBranchConditional %62 %54 %56 + %56 = OpLabel + OpBranch %37 + %37 = OpLabel + %63 = OpLoad %8 %33 + %64 = OpIAdd %8 %63 %29 + OpStore %33 %64 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + %6 = OpFunction %2 None %3 + %7 = OpLabel + %10 = OpVariable %9 Function + %12 = OpVariable %9 Function + OpStore %10 %11 + OpStore %12 %13 + OpBranch %14 + %14 = OpLabel + OpLoopMerge %16 %17 None + OpBranch %18 + %18 = OpLabel + %19 = OpLoad %8 %12 + %22 = OpSLessThan %21 %19 %20 + OpBranchConditional %22 %15 %16 + %15 = OpLabel + %23 = OpLoad %8 %12 + %24 = OpLoad %8 %10 + %25 = OpIAdd %8 %24 %23 + OpStore %10 %25 + %26 = OpLoad %8 %10 + %27 = OpIMul %8 %26 %11 + OpStore %10 %27 + OpBranch %17 + %17 = OpLabel + %28 = OpLoad %8 %12 + %30 = OpIAdd %8 %28 %29 + OpStore %12 %30 + OpBranch %14 + %16 = OpLabel + OpReturn + OpFunctionEnd + + )"; + + // Do 2 fuzz-and-shrink runs, starting from an initial seed of 2 (seed value + // chosen arbitrarily). + RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 2, 2); +} + +TEST(FuzzerShrinkerTest, Miscellaneous2) { + // The following SPIR-V came from this GLSL, which was then optimized using + // spirv-opt with the -O argument: + // + // #version 310 es + // + // precision highp float; + // + // layout(location = 0) out vec4 _GLF_color; + // + // layout(set = 0, binding = 0) uniform buf0 { + // vec2 injectionSwitch; + // }; + // layout(set = 0, binding = 1) uniform buf1 { + // vec2 resolution; + // }; + // bool checkSwap(float a, float b) + // { + // return gl_FragCoord.y < resolution.y / 2.0 ? a > b : a < b; + // } + // void main() + // { + // float data[10]; + // for(int i = 0; i < 10; i++) + // { + // data[i] = float(10 - i) * injectionSwitch.y; + // } + // for(int i = 0; i < 9; i++) + // { + // for(int j = 0; j < 10; j++) + // { + // if(j < i + 1) + // { + // continue; + // } + // bool doSwap = checkSwap(data[i], data[j]); + // if(doSwap) + // { + // float temp = data[i]; + // data[i] = data[j]; + // data[j] = temp; + // } + // } + // } + // if(gl_FragCoord.x < resolution.x / 2.0) + // { + // _GLF_color = vec4(data[0] / 10.0, data[5] / 10.0, data[9] / 10.0, 1.0); + // } + // else + // { + // _GLF_color = vec4(data[5] / 10.0, data[9] / 10.0, data[0] / 10.0, 1.0); + // } + // } + + const 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 + OpName %4 "main" + OpName %16 "gl_FragCoord" + OpName %23 "buf1" + OpMemberName %23 0 "resolution" + OpName %25 "" + OpName %61 "data" + OpName %66 "buf0" + OpMemberName %66 0 "injectionSwitch" + OpName %68 "" + OpName %139 "_GLF_color" + OpDecorate %16 BuiltIn FragCoord + OpMemberDecorate %23 0 Offset 0 + OpDecorate %23 Block + OpDecorate %25 DescriptorSet 0 + OpDecorate %25 Binding 1 + OpDecorate %64 RelaxedPrecision + OpMemberDecorate %66 0 Offset 0 + OpDecorate %66 Block + OpDecorate %68 DescriptorSet 0 + OpDecorate %68 Binding 0 + OpDecorate %75 RelaxedPrecision + OpDecorate %95 RelaxedPrecision + OpDecorate %126 RelaxedPrecision + OpDecorate %128 RelaxedPrecision + OpDecorate %139 Location 0 + OpDecorate %182 RelaxedPrecision + OpDecorate %183 RelaxedPrecision + OpDecorate %184 RelaxedPrecision + %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 + %17 = OpTypeInt 32 0 + %18 = OpConstant %17 1 + %19 = OpTypePointer Input %6 + %22 = OpTypeVector %6 2 + %23 = OpTypeStruct %22 + %24 = OpTypePointer Uniform %23 + %25 = OpVariable %24 Uniform + %26 = OpTypeInt 32 1 + %27 = OpConstant %26 0 + %28 = OpTypePointer Uniform %6 + %56 = OpConstant %26 10 + %58 = OpConstant %17 10 + %59 = OpTypeArray %6 %58 + %60 = OpTypePointer Function %59 + %66 = OpTypeStruct %22 + %67 = OpTypePointer Uniform %66 + %68 = OpVariable %67 Uniform + %74 = OpConstant %26 1 + %83 = OpConstant %26 9 + %129 = OpConstant %17 0 + %138 = OpTypePointer Output %14 + %139 = OpVariable %138 Output + %144 = OpConstant %26 5 + %151 = OpConstant %6 1 + %194 = OpConstant %6 0.5 + %195 = OpConstant %6 0.100000001 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %61 = OpVariable %60 Function + OpBranch %50 + %50 = OpLabel + %182 = OpPhi %26 %27 %5 %75 %51 + %57 = OpSLessThan %8 %182 %56 + OpLoopMerge %52 %51 None + OpBranchConditional %57 %51 %52 + %51 = OpLabel + %64 = OpISub %26 %56 %182 + %65 = OpConvertSToF %6 %64 + %69 = OpAccessChain %28 %68 %27 %18 + %70 = OpLoad %6 %69 + %71 = OpFMul %6 %65 %70 + %72 = OpAccessChain %7 %61 %182 + OpStore %72 %71 + %75 = OpIAdd %26 %182 %74 + OpBranch %50 + %52 = OpLabel + OpBranch %77 + %77 = OpLabel + %183 = OpPhi %26 %27 %52 %128 %88 + %84 = OpSLessThan %8 %183 %83 + OpLoopMerge %79 %88 None + OpBranchConditional %84 %78 %79 + %78 = OpLabel + OpBranch %86 + %86 = OpLabel + %184 = OpPhi %26 %27 %78 %126 %89 + %92 = OpSLessThan %8 %184 %56 + OpLoopMerge %88 %89 None + OpBranchConditional %92 %87 %88 + %87 = OpLabel + %95 = OpIAdd %26 %183 %74 + %96 = OpSLessThan %8 %184 %95 + OpSelectionMerge %98 None + OpBranchConditional %96 %97 %98 + %97 = OpLabel + OpBranch %89 + %98 = OpLabel + %104 = OpAccessChain %7 %61 %183 + %105 = OpLoad %6 %104 + %107 = OpAccessChain %7 %61 %184 + %108 = OpLoad %6 %107 + %166 = OpAccessChain %19 %16 %18 + %167 = OpLoad %6 %166 + %168 = OpAccessChain %28 %25 %27 %18 + %169 = OpLoad %6 %168 + %170 = OpFMul %6 %169 %194 + %171 = OpFOrdLessThan %8 %167 %170 + OpSelectionMerge %172 None + OpBranchConditional %171 %173 %174 + %173 = OpLabel + %177 = OpFOrdGreaterThan %8 %105 %108 + OpBranch %172 + %174 = OpLabel + %180 = OpFOrdLessThan %8 %105 %108 + OpBranch %172 + %172 = OpLabel + %186 = OpPhi %8 %177 %173 %180 %174 + OpSelectionMerge %112 None + OpBranchConditional %186 %111 %112 + %111 = OpLabel + %116 = OpLoad %6 %104 + %120 = OpLoad %6 %107 + OpStore %104 %120 + OpStore %107 %116 + OpBranch %112 + %112 = OpLabel + OpBranch %89 + %89 = OpLabel + %126 = OpIAdd %26 %184 %74 + OpBranch %86 + %88 = OpLabel + %128 = OpIAdd %26 %183 %74 + OpBranch %77 + %79 = OpLabel + %130 = OpAccessChain %19 %16 %129 + %131 = OpLoad %6 %130 + %132 = OpAccessChain %28 %25 %27 %129 + %133 = OpLoad %6 %132 + %134 = OpFMul %6 %133 %194 + %135 = OpFOrdLessThan %8 %131 %134 + OpSelectionMerge %137 None + OpBranchConditional %135 %136 %153 + %136 = OpLabel + %140 = OpAccessChain %7 %61 %27 + %141 = OpLoad %6 %140 + %143 = OpFMul %6 %141 %195 + %145 = OpAccessChain %7 %61 %144 + %146 = OpLoad %6 %145 + %147 = OpFMul %6 %146 %195 + %148 = OpAccessChain %7 %61 %83 + %149 = OpLoad %6 %148 + %150 = OpFMul %6 %149 %195 + %152 = OpCompositeConstruct %14 %143 %147 %150 %151 + OpStore %139 %152 + OpBranch %137 + %153 = OpLabel + %154 = OpAccessChain %7 %61 %144 + %155 = OpLoad %6 %154 + %156 = OpFMul %6 %155 %195 + %157 = OpAccessChain %7 %61 %83 + %158 = OpLoad %6 %157 + %159 = OpFMul %6 %158 %195 + %160 = OpAccessChain %7 %61 %27 + %161 = OpLoad %6 %160 + %162 = OpFMul %6 %161 %195 + %163 = OpCompositeConstruct %14 %156 %159 %162 %151 + OpStore %139 %163 + OpBranch %137 + %137 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Do 2 fuzzer runs, starting from an initial seed of 19 (seed value chosen + // arbitrarily). + RunFuzzerAndShrinker(shader, protobufs::FactSequence(), 19, 2); +} + +TEST(FuzzerShrinkerTest, Miscellaneous3) { + // The following SPIR-V came from this GLSL, which was then optimized using + // spirv-opt with the -O argument: + // + // #version 310 es + // + // precision highp float; + // + // layout(location = 0) out vec4 _GLF_color; + // + // layout(set = 0, binding = 0) uniform buf0 { + // vec2 resolution; + // }; + // void main(void) + // { + // float A[50]; + // for( + // int i = 0; + // i < 200; + // i ++ + // ) + // { + // if(i >= int(resolution.x)) + // { + // break; + // } + // if((4 * (i / 4)) == i) + // { + // A[i / 4] = float(i); + // } + // } + // for( + // int i = 0; + // i < 50; + // i ++ + // ) + // { + // if(i < int(gl_FragCoord.x)) + // { + // break; + // } + // if(i > 0) + // { + // A[i] += A[i - 1]; + // } + // } + // if(int(gl_FragCoord.x) < 20) + // { + // _GLF_color = vec4(A[0] / resolution.x, A[4] / resolution.y, 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 40) + // { + // _GLF_color = vec4(A[5] / resolution.x, A[9] / resolution.y, 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 60) + // { + // _GLF_color = vec4(A[10] / resolution.x, A[14] / resolution.y, + // 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 80) + // { + // _GLF_color = vec4(A[15] / resolution.x, A[19] / resolution.y, + // 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 100) + // { + // _GLF_color = vec4(A[20] / resolution.x, A[24] / resolution.y, + // 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 120) + // { + // _GLF_color = vec4(A[25] / resolution.x, A[29] / resolution.y, + // 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 140) + // { + // _GLF_color = vec4(A[30] / resolution.x, A[34] / resolution.y, + // 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 160) + // { + // _GLF_color = vec4(A[35] / resolution.x, A[39] / + // resolution.y, 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 180) + // { + // _GLF_color = vec4(A[40] / resolution.x, A[44] / + // resolution.y, 1.0, 1.0); + // } + // else + // if(int(gl_FragCoord.x) < 180) + // { + // _GLF_color = vec4(A[45] / resolution.x, A[49] / + // resolution.y, 1.0, 1.0); + // } + // else + // { + // discard; + // } + // } + + const std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %68 %100 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + OpName %22 "buf0" + OpMemberName %22 0 "resolution" + OpName %24 "" + OpName %46 "A" + OpName %68 "gl_FragCoord" + OpName %100 "_GLF_color" + OpMemberDecorate %22 0 Offset 0 + OpDecorate %22 Block + OpDecorate %24 DescriptorSet 0 + OpDecorate %24 Binding 0 + OpDecorate %37 RelaxedPrecision + OpDecorate %38 RelaxedPrecision + OpDecorate %55 RelaxedPrecision + OpDecorate %68 BuiltIn FragCoord + OpDecorate %83 RelaxedPrecision + OpDecorate %91 RelaxedPrecision + OpDecorate %100 Location 0 + OpDecorate %302 RelaxedPrecision + OpDecorate %304 RelaxedPrecision + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %9 = OpConstant %6 0 + %16 = OpConstant %6 200 + %17 = OpTypeBool + %20 = OpTypeFloat 32 + %21 = OpTypeVector %20 2 + %22 = OpTypeStruct %21 + %23 = OpTypePointer Uniform %22 + %24 = OpVariable %23 Uniform + %25 = OpTypeInt 32 0 + %26 = OpConstant %25 0 + %27 = OpTypePointer Uniform %20 + %35 = OpConstant %6 4 + %43 = OpConstant %25 50 + %44 = OpTypeArray %20 %43 + %45 = OpTypePointer Function %44 + %51 = OpTypePointer Function %20 + %54 = OpConstant %6 1 + %63 = OpConstant %6 50 + %66 = OpTypeVector %20 4 + %67 = OpTypePointer Input %66 + %68 = OpVariable %67 Input + %69 = OpTypePointer Input %20 + %95 = OpConstant %6 20 + %99 = OpTypePointer Output %66 + %100 = OpVariable %99 Output + %108 = OpConstant %25 1 + %112 = OpConstant %20 1 + %118 = OpConstant %6 40 + %122 = OpConstant %6 5 + %128 = OpConstant %6 9 + %139 = OpConstant %6 60 + %143 = OpConstant %6 10 + %149 = OpConstant %6 14 + %160 = OpConstant %6 80 + %164 = OpConstant %6 15 + %170 = OpConstant %6 19 + %181 = OpConstant %6 100 + %190 = OpConstant %6 24 + %201 = OpConstant %6 120 + %205 = OpConstant %6 25 + %211 = OpConstant %6 29 + %222 = OpConstant %6 140 + %226 = OpConstant %6 30 + %232 = OpConstant %6 34 + %243 = OpConstant %6 160 + %247 = OpConstant %6 35 + %253 = OpConstant %6 39 + %264 = OpConstant %6 180 + %273 = OpConstant %6 44 + %287 = OpConstant %6 45 + %293 = OpConstant %6 49 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %46 = OpVariable %45 Function + OpBranch %10 + %10 = OpLabel + %302 = OpPhi %6 %9 %5 %55 %42 + %18 = OpSLessThan %17 %302 %16 + OpLoopMerge %12 %42 None + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %28 = OpAccessChain %27 %24 %9 %26 + %29 = OpLoad %20 %28 + %30 = OpConvertFToS %6 %29 + %31 = OpSGreaterThanEqual %17 %302 %30 + OpSelectionMerge %33 None + OpBranchConditional %31 %32 %33 + %32 = OpLabel + OpBranch %12 + %33 = OpLabel + %37 = OpSDiv %6 %302 %35 + %38 = OpIMul %6 %35 %37 + %40 = OpIEqual %17 %38 %302 + OpSelectionMerge %42 None + OpBranchConditional %40 %41 %42 + %41 = OpLabel + %50 = OpConvertSToF %20 %302 + %52 = OpAccessChain %51 %46 %37 + OpStore %52 %50 + OpBranch %42 + %42 = OpLabel + %55 = OpIAdd %6 %302 %54 + OpBranch %10 + %12 = OpLabel + OpBranch %57 + %57 = OpLabel + %304 = OpPhi %6 %9 %12 %91 %80 + %64 = OpSLessThan %17 %304 %63 + OpLoopMerge %59 %80 None + OpBranchConditional %64 %58 %59 + %58 = OpLabel + %70 = OpAccessChain %69 %68 %26 + %71 = OpLoad %20 %70 + %72 = OpConvertFToS %6 %71 + %73 = OpSLessThan %17 %304 %72 + OpSelectionMerge %75 None + OpBranchConditional %73 %74 %75 + %74 = OpLabel + OpBranch %59 + %75 = OpLabel + %78 = OpSGreaterThan %17 %304 %9 + OpSelectionMerge %80 None + OpBranchConditional %78 %79 %80 + %79 = OpLabel + %83 = OpISub %6 %304 %54 + %84 = OpAccessChain %51 %46 %83 + %85 = OpLoad %20 %84 + %86 = OpAccessChain %51 %46 %304 + %87 = OpLoad %20 %86 + %88 = OpFAdd %20 %87 %85 + OpStore %86 %88 + OpBranch %80 + %80 = OpLabel + %91 = OpIAdd %6 %304 %54 + OpBranch %57 + %59 = OpLabel + %92 = OpAccessChain %69 %68 %26 + %93 = OpLoad %20 %92 + %94 = OpConvertFToS %6 %93 + %96 = OpSLessThan %17 %94 %95 + OpSelectionMerge %98 None + OpBranchConditional %96 %97 %114 + %97 = OpLabel + %101 = OpAccessChain %51 %46 %9 + %102 = OpLoad %20 %101 + %103 = OpAccessChain %27 %24 %9 %26 + %104 = OpLoad %20 %103 + %105 = OpFDiv %20 %102 %104 + %106 = OpAccessChain %51 %46 %35 + %107 = OpLoad %20 %106 + %109 = OpAccessChain %27 %24 %9 %108 + %110 = OpLoad %20 %109 + %111 = OpFDiv %20 %107 %110 + %113 = OpCompositeConstruct %66 %105 %111 %112 %112 + OpStore %100 %113 + OpBranch %98 + %114 = OpLabel + %119 = OpSLessThan %17 %94 %118 + OpSelectionMerge %121 None + OpBranchConditional %119 %120 %135 + %120 = OpLabel + %123 = OpAccessChain %51 %46 %122 + %124 = OpLoad %20 %123 + %125 = OpAccessChain %27 %24 %9 %26 + %126 = OpLoad %20 %125 + %127 = OpFDiv %20 %124 %126 + %129 = OpAccessChain %51 %46 %128 + %130 = OpLoad %20 %129 + %131 = OpAccessChain %27 %24 %9 %108 + %132 = OpLoad %20 %131 + %133 = OpFDiv %20 %130 %132 + %134 = OpCompositeConstruct %66 %127 %133 %112 %112 + OpStore %100 %134 + OpBranch %121 + %135 = OpLabel + %140 = OpSLessThan %17 %94 %139 + OpSelectionMerge %142 None + OpBranchConditional %140 %141 %156 + %141 = OpLabel + %144 = OpAccessChain %51 %46 %143 + %145 = OpLoad %20 %144 + %146 = OpAccessChain %27 %24 %9 %26 + %147 = OpLoad %20 %146 + %148 = OpFDiv %20 %145 %147 + %150 = OpAccessChain %51 %46 %149 + %151 = OpLoad %20 %150 + %152 = OpAccessChain %27 %24 %9 %108 + %153 = OpLoad %20 %152 + %154 = OpFDiv %20 %151 %153 + %155 = OpCompositeConstruct %66 %148 %154 %112 %112 + OpStore %100 %155 + OpBranch %142 + %156 = OpLabel + %161 = OpSLessThan %17 %94 %160 + OpSelectionMerge %163 None + OpBranchConditional %161 %162 %177 + %162 = OpLabel + %165 = OpAccessChain %51 %46 %164 + %166 = OpLoad %20 %165 + %167 = OpAccessChain %27 %24 %9 %26 + %168 = OpLoad %20 %167 + %169 = OpFDiv %20 %166 %168 + %171 = OpAccessChain %51 %46 %170 + %172 = OpLoad %20 %171 + %173 = OpAccessChain %27 %24 %9 %108 + %174 = OpLoad %20 %173 + %175 = OpFDiv %20 %172 %174 + %176 = OpCompositeConstruct %66 %169 %175 %112 %112 + OpStore %100 %176 + OpBranch %163 + %177 = OpLabel + %182 = OpSLessThan %17 %94 %181 + OpSelectionMerge %184 None + OpBranchConditional %182 %183 %197 + %183 = OpLabel + %185 = OpAccessChain %51 %46 %95 + %186 = OpLoad %20 %185 + %187 = OpAccessChain %27 %24 %9 %26 + %188 = OpLoad %20 %187 + %189 = OpFDiv %20 %186 %188 + %191 = OpAccessChain %51 %46 %190 + %192 = OpLoad %20 %191 + %193 = OpAccessChain %27 %24 %9 %108 + %194 = OpLoad %20 %193 + %195 = OpFDiv %20 %192 %194 + %196 = OpCompositeConstruct %66 %189 %195 %112 %112 + OpStore %100 %196 + OpBranch %184 + %197 = OpLabel + %202 = OpSLessThan %17 %94 %201 + OpSelectionMerge %204 None + OpBranchConditional %202 %203 %218 + %203 = OpLabel + %206 = OpAccessChain %51 %46 %205 + %207 = OpLoad %20 %206 + %208 = OpAccessChain %27 %24 %9 %26 + %209 = OpLoad %20 %208 + %210 = OpFDiv %20 %207 %209 + %212 = OpAccessChain %51 %46 %211 + %213 = OpLoad %20 %212 + %214 = OpAccessChain %27 %24 %9 %108 + %215 = OpLoad %20 %214 + %216 = OpFDiv %20 %213 %215 + %217 = OpCompositeConstruct %66 %210 %216 %112 %112 + OpStore %100 %217 + OpBranch %204 + %218 = OpLabel + %223 = OpSLessThan %17 %94 %222 + OpSelectionMerge %225 None + OpBranchConditional %223 %224 %239 + %224 = OpLabel + %227 = OpAccessChain %51 %46 %226 + %228 = OpLoad %20 %227 + %229 = OpAccessChain %27 %24 %9 %26 + %230 = OpLoad %20 %229 + %231 = OpFDiv %20 %228 %230 + %233 = OpAccessChain %51 %46 %232 + %234 = OpLoad %20 %233 + %235 = OpAccessChain %27 %24 %9 %108 + %236 = OpLoad %20 %235 + %237 = OpFDiv %20 %234 %236 + %238 = OpCompositeConstruct %66 %231 %237 %112 %112 + OpStore %100 %238 + OpBranch %225 + %239 = OpLabel + %244 = OpSLessThan %17 %94 %243 + OpSelectionMerge %246 None + OpBranchConditional %244 %245 %260 + %245 = OpLabel + %248 = OpAccessChain %51 %46 %247 + %249 = OpLoad %20 %248 + %250 = OpAccessChain %27 %24 %9 %26 + %251 = OpLoad %20 %250 + %252 = OpFDiv %20 %249 %251 + %254 = OpAccessChain %51 %46 %253 + %255 = OpLoad %20 %254 + %256 = OpAccessChain %27 %24 %9 %108 + %257 = OpLoad %20 %256 + %258 = OpFDiv %20 %255 %257 + %259 = OpCompositeConstruct %66 %252 %258 %112 %112 + OpStore %100 %259 + OpBranch %246 + %260 = OpLabel + %265 = OpSLessThan %17 %94 %264 + OpSelectionMerge %267 None + OpBranchConditional %265 %266 %280 + %266 = OpLabel + %268 = OpAccessChain %51 %46 %118 + %269 = OpLoad %20 %268 + %270 = OpAccessChain %27 %24 %9 %26 + %271 = OpLoad %20 %270 + %272 = OpFDiv %20 %269 %271 + %274 = OpAccessChain %51 %46 %273 + %275 = OpLoad %20 %274 + %276 = OpAccessChain %27 %24 %9 %108 + %277 = OpLoad %20 %276 + %278 = OpFDiv %20 %275 %277 + %279 = OpCompositeConstruct %66 %272 %278 %112 %112 + OpStore %100 %279 + OpBranch %267 + %280 = OpLabel + OpSelectionMerge %285 None + OpBranchConditional %265 %285 %300 + %285 = OpLabel + %288 = OpAccessChain %51 %46 %287 + %289 = OpLoad %20 %288 + %290 = OpAccessChain %27 %24 %9 %26 + %291 = OpLoad %20 %290 + %292 = OpFDiv %20 %289 %291 + %294 = OpAccessChain %51 %46 %293 + %295 = OpLoad %20 %294 + %296 = OpAccessChain %27 %24 %9 %108 + %297 = OpLoad %20 %296 + %298 = OpFDiv %20 %295 %297 + %299 = OpCompositeConstruct %66 %292 %298 %112 %112 + OpStore %100 %299 + OpBranch %267 + %300 = OpLabel + OpKill + %267 = OpLabel + OpBranch %246 + %246 = OpLabel + OpBranch %225 + %225 = OpLabel + OpBranch %204 + %204 = OpLabel + OpBranch %184 + %184 = OpLabel + OpBranch %163 + %163 = OpLabel + OpBranch %142 + %142 = OpLabel + OpBranch %121 + %121 = OpLabel + OpBranch %98 + %98 = OpLabel + OpReturn + OpFunctionEnd + )"; + + // Add the facts "resolution.x == 250" and "resolution.y == 100". + protobufs::FactSequence facts; + { + protobufs::FactConstantUniform resolution_x_eq_250; + *resolution_x_eq_250.mutable_uniform_buffer_element_descriptor() = + MakeUniformBufferElementDescriptor(0, 0, {0, 0}); + *resolution_x_eq_250.mutable_constant_word()->Add() = 250; + protobufs::Fact temp; + *temp.mutable_constant_uniform_fact() = resolution_x_eq_250; + *facts.mutable_fact()->Add() = temp; + } + { + protobufs::FactConstantUniform resolution_y_eq_100; + *resolution_y_eq_100.mutable_uniform_buffer_element_descriptor() = + MakeUniformBufferElementDescriptor(0, 0, {0, 1}); + *resolution_y_eq_100.mutable_constant_word()->Add() = 100; + protobufs::Fact temp; + *temp.mutable_constant_uniform_fact() = resolution_y_eq_100; + *facts.mutable_fact()->Add() = temp; + } + + // Do 2 fuzzer runs, starting from an initial seed of 194 (seed value chosen + // arbitrarily). + RunFuzzerAndShrinker(shader, facts, 194, 2); +} + +} // namespace +} // namespace fuzz +} // namespace spvtools 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 4f89487d5..f3238015b 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 @@ -6496,6 +6496,70 @@ OpFunctionEnd SinglePassRunAndMatch(spirv, true); } +TEST_F(AggressiveDCETest, PreserveBindings) { + const std::string spirv = R"( +; CHECK: OpDecorate %unusedSampler DescriptorSet 0 +; CHECK: OpDecorate %unusedSampler Binding 0 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %unusedSampler "unusedSampler" +OpDecorate %unusedSampler DescriptorSet 0 +OpDecorate %unusedSampler Binding 0 +%void = OpTypeVoid +%5 = OpTypeFunction %void +%float = OpTypeFloat 32 +%7 = OpTypeImage %float 2D 0 0 0 1 Unknown +%8 = OpTypeSampledImage %7 +%_ptr_UniformConstant_8 = OpTypePointer UniformConstant %8 +%unusedSampler = OpVariable %_ptr_UniformConstant_8 UniformConstant +%main = OpFunction %void None %5 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_UNIVERSAL_1_4); + + OptimizerOptions()->preserve_bindings_ = true; + + SinglePassRunAndMatch(spirv, true); +} + +TEST_F(AggressiveDCETest, PreserveSpecConstants) { + const std::string spirv = R"( +; CHECK: OpName %specConstant "specConstant" +; CHECK: %specConstant = OpSpecConstant %int 0 +OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 430 +OpName %main "main" +OpName %specConstant "specConstant" +OpDecorate %specConstant SpecId 0 +%void = OpTypeVoid +%3 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%specConstant = OpSpecConstant %int 0 +%main = OpFunction %void None %3 +%5 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SetTargetEnv(SPV_ENV_UNIVERSAL_1_4); + + OptimizerOptions()->preserve_spec_constants_ = true; + + SinglePassRunAndMatch(spirv, 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/dead_branch_elim_test.cpp b/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp index 640d4e8a9..5d41bdc10 100644 --- a/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp +++ b/3rdparty/spirv-tools/test/opt/dead_branch_elim_test.cpp @@ -3081,6 +3081,82 @@ TEST_F(DeadBranchElimTest, FoldBranchWithBreakToSwitch) { SinglePassRunAndMatch(spirv, true); } +TEST_F(DeadBranchElimTest, IfInSwitch) { + // #version 310 es + // + // void main() + // { + // switch(0) + // { + // case 0: + // if(false) + // { + // } + // else + // { + // } + // } + // } + + const std::string before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource ESSL 310 +OpName %main "main" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%bool = OpTypeBool +%false = OpConstantFalse %bool +%main = OpFunction %void None %3 +%5 = OpLabel +OpSelectionMerge %9 None +OpSwitch %int_0 %9 0 %8 +%8 = OpLabel +OpSelectionMerge %13 None +OpBranchConditional %false %12 %13 +%12 = OpLabel +OpBranch %13 +%13 = OpLabel +OpBranch %9 +%9 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource ESSL 310 +OpName %main "main" +%void = OpTypeVoid +%4 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%int_0 = OpConstant %int 0 +%bool = OpTypeBool +%false = OpConstantFalse %bool +%main = OpFunction %void None %4 +%9 = OpLabel +OpBranch %11 +%11 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranch %10 +%10 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // More complex control flow diff --git a/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp index 3ae6458ba..3eb3ef58e 100644 --- a/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp +++ b/3rdparty/spirv-tools/test/opt/decoration_manager_test.cpp @@ -1277,6 +1277,232 @@ OpDecorateStringGOOGLE %2 HlslSemanticGOOGLE "hello" EXPECT_FALSE(decoManager->HaveTheSameDecorations(1u, 2u)); } +TEST_F(DecorationManagerTest, SubSetTestOpDecorate1) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %2 Restrict +OpDecorate %1 Constant +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %2 Restrict +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate3) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Constant +OpDecorate %2 Restrict +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate4) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %2 Restrict +OpDecorate %1 Constant +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate5) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Restrict +OpDecorate %2 Constant +OpDecorate %2 Restrict +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate6) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Constant +OpDecorate %2 Restrict +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorate7) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorate %1 Constant +OpDecorate %2 Constant +OpDecorate %2 Restrict +OpDecorate %1 Invariant +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%2 = OpVariable %u32 Uniform +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u)); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpMemberDecorate1) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %1 0 Offset 0 +OpMemberDecorate %1 0 Offset 4 +OpMemberDecorate %2 0 Offset 0 +OpMemberDecorate %2 0 Offset 4 +%u32 = OpTypeInt 32 0 +%1 = OpTypeStruct %u32 %u32 %u32 +%2 = OpTypeStruct %u32 %u32 %u32 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u)); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpMemberDecorate2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpMemberDecorate %1 0 Offset 0 +OpMemberDecorate %2 0 Offset 0 +OpMemberDecorate %2 0 Offset 4 +%u32 = OpTypeInt 32 0 +%1 = OpTypeStruct %u32 %u32 %u32 +%2 = OpTypeStruct %u32 %u32 %u32 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(1u, 2u)); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorateId1) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %2 +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%3 = OpVariable %u32 Uniform +%2 = OpSpecConstant %u32 0 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 3u)); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(3u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorateId2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpMemoryModel Logical GLSL450 +OpDecorateId %1 AlignmentId %2 +OpDecorateId %3 AlignmentId %4 +%u32 = OpTypeInt 32 0 +%1 = OpVariable %u32 Uniform +%3 = OpVariable %u32 Uniform +%2 = OpSpecConstant %u32 0 +%4 = OpSpecConstant %u32 1 +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 3u)); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(3u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorateString1) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateString %1 HlslSemanticGOOGLE "hello" +OpDecorateString %2 HlslSemanticGOOGLE "world" +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u)); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} + +TEST_F(DecorationManagerTest, SubSetTestOpDecorateString2) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpExtension "SPV_GOOGLE_hlsl_functionality1" +OpExtension "SPV_GOOGLE_decorate_string" +OpMemoryModel Logical GLSL450 +OpDecorateString %1 HlslSemanticGOOGLE "hello" +)"; + DecorationManager* decoManager = GetDecorationManager(spirv); + EXPECT_THAT(GetErrorMessage(), ""); + EXPECT_FALSE(decoManager->HaveSubsetOfDecorations(1u, 2u)); + EXPECT_TRUE(decoManager->HaveSubsetOfDecorations(2u, 1u)); +} } // namespace } // namespace analysis } // namespace opt diff --git a/3rdparty/spirv-tools/test/opt/inline_test.cpp b/3rdparty/spirv-tools/test/opt/inline_test.cpp index 717081210..fd38e4776 100644 --- a/3rdparty/spirv-tools/test/opt/inline_test.cpp +++ b/3rdparty/spirv-tools/test/opt/inline_test.cpp @@ -3112,6 +3112,121 @@ OpFunctionEnd SinglePassRunAndCheck(test, test, false, true); } +TEST_F(InlineTest, DontInlineFuncWithOpKill) { + const std::string test = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 330 +OpName %main "main" +OpName %kill_ "kill(" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %void None %3 +%5 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %11 %12 None +OpBranch %13 +%13 = OpLabel +OpBranchConditional %true %10 %11 +%10 = OpLabel +OpBranch %12 +%12 = OpLabel +%16 = OpFunctionCall %void %kill_ +OpBranch %9 +%11 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %void None %3 +%7 = OpLabel +OpKill +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(test, test, false, true); +} + +TEST_F(InlineTest, InlineFuncWithOpKill) { + const std::string before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 330 +OpName %main "main" +OpName %kill_ "kill(" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %void None %3 +%5 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %11 %12 None +OpBranch %13 +%13 = OpLabel +OpBranchConditional %true %10 %11 +%10 = OpLabel +%16 = OpFunctionCall %void %kill_ +OpBranch %12 +%12 = OpLabel +OpBranch %9 +%11 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %void None %3 +%7 = OpLabel +OpKill +OpFunctionEnd +)"; + const std::string after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 330 +OpName %main "main" +OpName %kill_ "kill(" +%void = OpTypeVoid +%3 = OpTypeFunction %void +%bool = OpTypeBool +%true = OpConstantTrue %bool +%main = OpFunction %void None %3 +%5 = OpLabel +OpBranch %9 +%9 = OpLabel +OpLoopMerge %11 %12 None +OpBranch %13 +%13 = OpLabel +OpBranchConditional %true %10 %11 +%10 = OpLabel +OpKill +%17 = OpLabel +OpBranch %12 +%12 = OpLabel +OpBranch %9 +%11 = OpLabel +OpReturn +OpFunctionEnd +%kill_ = OpFunction %void None %3 +%7 = OpLabel +OpKill +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndCheck(before, after, false, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: // // Empty modules diff --git a/3rdparty/spirv-tools/test/opt/pass_fixture.h b/3rdparty/spirv-tools/test/opt/pass_fixture.h index 10e7c53fe..b7a07426b 100644 --- a/3rdparty/spirv-tools/test/opt/pass_fixture.h +++ b/3rdparty/spirv-tools/test/opt/pass_fixture.h @@ -27,6 +27,7 @@ #include "source/opt/build_module.h" #include "source/opt/pass_manager.h" #include "source/opt/passes.h" +#include "source/spirv_optimizer_options.h" #include "source/spirv_validator_options.h" #include "source/util/make_unique.h" #include "spirv-tools/libspirv.hpp" @@ -67,6 +68,10 @@ class PassTest : public TestT { return std::make_tuple(std::vector(), Pass::Status::Failure); } + context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_); + context()->set_preserve_spec_constants( + OptimizerOptions()->preserve_spec_constants_); + const auto status = pass->Run(context()); std::vector binary; @@ -206,6 +211,10 @@ class PassTest : public TestT { std::move(BuildModule(env_, nullptr, original, assemble_options_)); ASSERT_NE(nullptr, context()); + context()->set_preserve_bindings(OptimizerOptions()->preserve_bindings_); + context()->set_preserve_spec_constants( + OptimizerOptions()->preserve_spec_constants_); + manager_->Run(context()); std::vector binary; @@ -232,6 +241,8 @@ class PassTest : public TestT { consumer_ = msg_consumer; } + spv_optimizer_options OptimizerOptions() { return &optimizer_options_; } + spv_validator_options ValidatorOptions() { return &validator_options_; } void SetTargetEnv(spv_target_env env) { env_ = env; } @@ -242,6 +253,7 @@ class PassTest : public TestT { std::unique_ptr manager_; // The pass manager. uint32_t assemble_options_; uint32_t disassemble_options_; + spv_optimizer_options_t optimizer_options_; spv_validator_options_t validator_options_; spv_target_env env_; }; 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 2f2e74a99..423e81d94 100644 --- a/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp +++ b/3rdparty/spirv-tools/test/opt/pass_merge_return_test.cpp @@ -1672,6 +1672,58 @@ OpFunctionEnd SinglePassRunAndMatch(spirv, true); } +TEST_F(MergeReturnPassTest, SingleReturnInLoop) { + const std::string predefs = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +OpSource ESSL 310 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%9 = OpTypeFunction %float +%float_1 = OpConstant %float 1 +)"; + + const std::string caller = + R"( +; CHECK: OpFunction +; CHECK: OpFunctionEnd +%main = OpFunction %void None %7 +%22 = OpLabel +%30 = OpFunctionCall %float %f_ +OpReturn +OpFunctionEnd +)"; + + const std::string callee = + R"( +; CHECK: OpFunction +; CHECK: OpLoopMerge [[merge:%\w+]] +; CHECK: [[merge]] = OpLabel +; CHECK: OpReturnValue +; CHECK-NEXT: OpFunctionEnd +%f_ = OpFunction %float None %9 +%33 = OpLabel +OpBranch %34 +%34 = OpLabel +OpLoopMerge %35 %36 None +OpBranch %37 +%37 = OpLabel +OpReturnValue %float_1 +%36 = OpLabel +OpBranch %34 +%35 = OpLabel +OpUnreachable +OpFunctionEnd +)"; + + SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + SinglePassRunAndMatch(predefs + caller + callee, 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 b7d6f18c6..4dbcfbe39 100644 --- a/3rdparty/spirv-tools/test/opt/simplification_test.cpp +++ b/3rdparty/spirv-tools/test/opt/simplification_test.cpp @@ -202,6 +202,83 @@ TEST_F(SimplificationTest, ThroughLoops) { SinglePassRunAndMatch(text, false); } +TEST_F(SimplificationTest, CopyObjectWithDecorations1) { + // Don't simplify OpCopyObject if the result id has a decoration that the + // operand does not. + const std::string text = R"(OpCapability Shader +OpCapability ShaderNonUniformEXT +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpDecorate %3 NonUniformEXT +%void = OpTypeVoid +%5 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%2 = OpFunction %void None %5 +%7 = OpLabel +%8 = OpUndef %int +%3 = OpCopyObject %int %8 +%9 = OpIAdd %int %3 %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(text, text, false); +} + +TEST_F(SimplificationTest, CopyObjectWithDecorations2) { + // Simplify OpCopyObject if the result id is a subset of the decorations of + // the operand. + const std::string before = R"(OpCapability Shader +OpCapability ShaderNonUniformEXT +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpDecorate %3 NonUniformEXT +%void = OpTypeVoid +%5 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%2 = OpFunction %void None %5 +%7 = OpLabel +%3 = OpUndef %int +%8 = OpCopyObject %int %3 +%9 = OpIAdd %int %8 %8 +OpReturn +OpFunctionEnd +)"; + + const std::string after = R"(OpCapability Shader +OpCapability ShaderNonUniformEXT +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %2 "main" +OpExecutionMode %2 OriginUpperLeft +OpSource GLSL 430 +OpSourceExtension "GL_GOOGLE_cpp_style_line_directive" +OpSourceExtension "GL_GOOGLE_include_directive" +OpDecorate %3 NonUniformEXT +%void = OpTypeVoid +%5 = OpTypeFunction %void +%int = OpTypeInt 32 1 +%2 = OpFunction %void None %5 +%7 = OpLabel +%3 = OpUndef %int +%9 = OpIAdd %int %3 %3 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck(before, after, false); +} + } // namespace } // namespace opt } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/opt/value_table_test.cpp b/3rdparty/spirv-tools/test/opt/value_table_test.cpp index ef338ae7e..0b7530c08 100644 --- a/3rdparty/spirv-tools/test/opt/value_table_test.cpp +++ b/3rdparty/spirv-tools/test/opt/value_table_test.cpp @@ -455,6 +455,34 @@ TEST_F(ValueTableTest, CopyObject) { EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); } +TEST_F(ValueTableTest, CopyObjectWitDecoration) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpDecorate %3 NonUniformEXT + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Function %6 + %2 = OpFunction %4 None %5 + %8 = OpLabel + %9 = OpVariable %7 Function + %10 = OpLoad %6 %9 + %3 = OpCopyObject %6 %10 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ValueNumberTable vtable(context.get()); + Instruction* inst1 = context->get_def_use_mgr()->GetDef(10); + Instruction* inst2 = context->get_def_use_mgr()->GetDef(3); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); +} + // Test that a phi where the operands have the same value assigned that value // to the result of the phi. TEST_F(ValueTableTest, PhiTest1) { @@ -495,6 +523,45 @@ TEST_F(ValueTableTest, PhiTest1) { EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi)); } +TEST_F(ValueTableTest, PhiTest1WithDecoration) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource GLSL 430 + OpDecorate %3 NonUniformEXT + %4 = OpTypeVoid + %5 = OpTypeFunction %5 + %6 = OpTypeFloat 32 + %7 = OpTypePointer Uniform %6 + %8 = OpTypeBool + %9 = OpConstantTrue %8 + %10 = OpVariable %7 Uniform + %2 = OpFunction %4 None %5 + %11 = OpLabel + OpBranchConditional %9 %12 %13 + %12 = OpLabel + %14 = OpLoad %6 %10 + OpBranch %15 + %13 = OpLabel + %16 = OpLoad %6 %10 + OpBranch %15 + %15 = OpLabel + %3 = OpPhi %6 %14 %12 %16 %13 + OpReturn + OpFunctionEnd + )"; + auto context = BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text); + ValueNumberTable vtable(context.get()); + Instruction* inst1 = context->get_def_use_mgr()->GetDef(14); + Instruction* inst2 = context->get_def_use_mgr()->GetDef(16); + Instruction* phi = context->get_def_use_mgr()->GetDef(3); + EXPECT_EQ(vtable.GetValueNumber(inst1), vtable.GetValueNumber(inst2)); + EXPECT_NE(vtable.GetValueNumber(inst1), vtable.GetValueNumber(phi)); +} + // When the values for the inputs to a phi do not match, then the phi should // have its own value number. TEST_F(ValueTableTest, PhiTest2) { diff --git a/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp b/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp index 612b6573c..4051bac1e 100644 --- a/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp +++ b/3rdparty/spirv-tools/test/reduce/validation_during_reduction_test.cpp @@ -38,7 +38,7 @@ class BlindlyRemoveGlobalValuesReductionOpportunityFinder ~BlindlyRemoveGlobalValuesReductionOpportunityFinder() override = default; // The name of this pass. - std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; }; + std::string GetName() const final { return "BlindlyRemoveGlobalValuesPass"; } // Finds opportunities to remove all global values. Assuming they are all // referenced (directly or indirectly) from elsewhere in the module, each such @@ -99,7 +99,7 @@ class OpVariableDuplicatorReductionOpportunityFinder std::string GetName() const final { return "LocalVariableAdderReductionOpportunityFinder"; - }; + } std::vector> GetAvailableOpportunities( IRContext* context) const final { diff --git a/3rdparty/spirv-tools/test/timer_test.cpp b/3rdparty/spirv-tools/test/timer_test.cpp index e53af6653..84ab46dab 100644 --- a/3rdparty/spirv-tools/test/timer_test.cpp +++ b/3rdparty/spirv-tools/test/timer_test.cpp @@ -97,7 +97,7 @@ class MockCumulativeTimer : public CumulativeTimer { long PageFault() const override { return count_stop_ * 3600L; } // Calling Stop() does nothing but just increases |count_stop_| by 1. - void Stop() override { ++count_stop_; }; + void Stop() override { ++count_stop_; } private: unsigned int count_stop_; diff --git a/3rdparty/spirv-tools/test/val/CMakeLists.txt b/3rdparty/spirv-tools/test/val/CMakeLists.txt index 8f4bc3342..b52c764dd 100644 --- a/3rdparty/spirv-tools/test/val/CMakeLists.txt +++ b/3rdparty/spirv-tools/test/val/CMakeLists.txt @@ -61,6 +61,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop val_literals_test.cpp val_logicals_test.cpp val_memory_test.cpp + val_misc_test.cpp val_modes_test.cpp val_non_uniform_test.cpp val_opencl_test.cpp @@ -72,6 +73,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop add_spvtools_unittest(TARGET val_stuvw SRCS + val_small_type_uses_test.cpp val_ssa_test.cpp val_state_test.cpp val_storage_test.cpp diff --git a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp index f130e02ed..a0056141e 100644 --- a/3rdparty/spirv-tools/test/val/val_atomics_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_atomics_test.cpp @@ -397,7 +397,7 @@ TEST_F(ValidateAtomics, AtomicLoadWebGPUNonRelaxedFailure) { TEST_F(ValidateAtomics, AtomicLoadWebGPUSequentiallyConsistentFailure) { const std::string body = R"( -%val3 = OpAtomicLoad %u32 %u32_var %subgroup %sequentially_consistent +%val3 = OpAtomicLoad %u32 %u32_var %invocation %sequentially_consistent )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); @@ -1916,7 +1916,7 @@ TEST_F(ValidateAtomics, WebGPUCrossDeviceMemoryScopeBad) { EXPECT_THAT( getDiagnosticString(), HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Subgroup and QueuFamilyKHR\n" + "Workgroup, Invocation, and QueueFamilyKHR\n" " %34 = OpAtomicLoad %uint %29 %uint_0_0 %uint_0_1\n")); } @@ -1930,7 +1930,7 @@ TEST_F(ValidateAtomics, WebGPUDeviceMemoryScopeBad) { EXPECT_THAT( getDiagnosticString(), HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Subgroup and QueuFamilyKHR\n" + "Workgroup, Invocation, and QueueFamilyKHR\n" " %34 = OpAtomicLoad %uint %29 %uint_1_0 %uint_0_1\n")); } @@ -1943,18 +1943,9 @@ TEST_F(ValidateAtomics, WebGPUWorkgroupMemoryScopeGood) { EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); } -TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeGood) { +TEST_F(ValidateAtomics, WebGPUSubgroupMemoryScopeBad) { const std::string body = R"( %val1 = OpAtomicLoad %u32 %u32_var %subgroup %relaxed -)"; - - CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); - EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0)); -} - -TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) { - const std::string body = R"( -%val1 = OpAtomicLoad %u32 %u32_var %invocation %relaxed )"; CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0); @@ -1962,8 +1953,17 @@ TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeBad) { EXPECT_THAT( getDiagnosticString(), HasSubstr("AtomicLoad: in WebGPU environment Memory Scope is limited to " - "Workgroup, Subgroup and QueuFamilyKHR\n" - " %34 = OpAtomicLoad %uint %29 %uint_4 %uint_0_1\n")); + "Workgroup, Invocation, and QueueFamilyKHR\n" + " %34 = OpAtomicLoad %uint %29 %uint_3 %uint_0_1\n")); +} + +TEST_F(ValidateAtomics, WebGPUInvocationMemoryScopeGood) { + 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)); } 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 b28e51a85..221419766 100644 --- a/3rdparty/spirv-tools/test/val/val_barriers_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_barriers_test.cpp @@ -403,7 +403,7 @@ OpControlBarrier %device %workgroup %none "is limited to Workgroup and Subgroup")); } -TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeDevice) { +TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeDeviceBad) { const std::string body = R"( OpControlBarrier %device %workgroup %none )"; @@ -412,7 +412,19 @@ OpControlBarrier %device %workgroup %none ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0)); EXPECT_THAT(getDiagnosticString(), HasSubstr("ControlBarrier: in WebGPU environment Execution Scope " - "is limited to Workgroup and Subgroup")); + "is limited to Workgroup")); +} + +TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeSubgroupBad) { + const std::string body = R"( +OpControlBarrier %subgroup %workgroup %none +)"; + + 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 Execution Scope " + "is limited to Workgroup")); } TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) { diff --git a/3rdparty/spirv-tools/test/val/val_composites_test.cpp b/3rdparty/spirv-tools/test/val/val_composites_test.cpp index 309c19467..74b6f2095 100644 --- a/3rdparty/spirv-tools/test/val/val_composites_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_composites_test.cpp @@ -17,6 +17,7 @@ #include "gmock/gmock.h" #include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" #include "test/val/val_fixtures.h" namespace spvtools { @@ -25,6 +26,7 @@ namespace { using ::testing::HasSubstr; using ::testing::Not; +using ::testing::Values; using ValidateComposites = spvtest::ValidateBase; @@ -1771,6 +1773,211 @@ OpFunctionEnd EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4)); } +using ValidateSmallComposites = spvtest::ValidateBase; + +CodeGenerator GetSmallCompositesCodeGenerator() { + CodeGenerator generator; + generator.capabilities_ = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpCapability UniformAndStorageBuffer8BitAccess +)"; + generator.extensions_ = R"( +OpExtension "SPV_KHR_16bit_storage" +OpExtension "SPV_KHR_8bit_storage" +)"; + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + generator.before_types_ = R"( +OpDecorate %char_block Block +OpMemberDecorate %char_block 0 Offset 0 +OpDecorate %short_block Block +OpMemberDecorate %short_block 0 Offset 0 +OpDecorate %half_block Block +OpMemberDecorate %half_block 0 Offset 0 +)"; + generator.types_ = R"( +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%char = OpTypeInt 8 0 +%char2 = OpTypeVector %char 2 +%short = OpTypeInt 16 0 +%short2 = OpTypeVector %short 2 +%half = OpTypeFloat 16 +%half2 = OpTypeVector %half 2 +%char_block = OpTypeStruct %char2 +%short_block = OpTypeStruct %short2 +%half_block = OpTypeStruct %half2 +%ptr_ssbo_char_block = OpTypePointer StorageBuffer %char_block +%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2 +%ptr_ssbo_char = OpTypePointer StorageBuffer %char +%ptr_ssbo_short_block = OpTypePointer StorageBuffer %short_block +%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2 +%ptr_ssbo_short = OpTypePointer StorageBuffer %short +%ptr_ssbo_half_block = OpTypePointer StorageBuffer %half_block +%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2 +%ptr_ssbo_half = OpTypePointer StorageBuffer %half +%void_fn = OpTypeFunction %void +%char_var = OpVariable %ptr_ssbo_char_block StorageBuffer +%short_var = OpVariable %ptr_ssbo_short_block StorageBuffer +%half_var = OpVariable %ptr_ssbo_half_block StorageBuffer +)"; + generator.after_types_ = R"( +%func = OpFunction %void None %void_fn +%entry = OpLabel +%char2_gep = OpAccessChain %ptr_ssbo_char2 %char_var %int_0 +%ld_char2 = OpLoad %char2 %char2_gep +%char_gep = OpAccessChain %ptr_ssbo_char %char_var %int_0 %int_0 +%ld_char = OpLoad %char %char_gep +%short2_gep = OpAccessChain %ptr_ssbo_short2 %short_var %int_0 +%ld_short2 = OpLoad %short2 %short2_gep +%short_gep = OpAccessChain %ptr_ssbo_short %short_var %int_0 %int_0 +%ld_short = OpLoad %short %short_gep +%half2_gep = OpAccessChain %ptr_ssbo_half2 %half_var %int_0 +%ld_half2 = OpLoad %half2 %half2_gep +%half_gep = OpAccessChain %ptr_ssbo_half %half_var %int_0 %int_0 +%ld_half = OpLoad %half %half_gep +)"; + generator.add_at_the_end_ = R"( +OpReturn +OpFunctionEnd +)"; + return generator; +} + +TEST_P(ValidateSmallComposites, VectorExtractDynamic) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = + "%inst = OpVectorExtractDynamic %" + type + " %ld_" + type + "2 %int_0\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cannot extract from a vector of 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, VectorInsertDynamic) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = "%inst = OpVectorInsertDynamic %" + type + "2 %ld_" + + type + "2 %ld_" + type + " %int_0\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cannot insert into a vector of 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, VectorShuffle) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = "%inst = OpVectorShuffle %" + type + "2 %ld_" + type + + "2 %ld_" + type + "2 0 0\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cannot shuffle a vector of 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, CompositeConstruct) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = "%inst = OpCompositeConstruct %" + type + "2 %ld_" + type + + " %ld_" + type + "\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot create a composite containing 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, CompositeExtract) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = + "%inst = OpCompositeExtract %" + type + " %ld_" + type + "2 0\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot extract from a composite of 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, CompositeInsert) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = "%inst = OpCompositeInsert %" + type + "2 %ld_" + type + + " %ld_" + type + "2 0\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot insert into a composite of 8- or 16-bit types")); +} + +TEST_P(ValidateSmallComposites, CopyObject) { + std::string type = GetParam(); + CodeGenerator generator = GetSmallCompositesCodeGenerator(); + std::string inst = "%inst = OpCopyObject %" + type + "2 %ld_" + type + "2\n"; + generator.after_types_ += inst; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +INSTANTIATE_TEST_SUITE_P(SmallCompositeInstructions, ValidateSmallComposites, + Values("char", "short", "half")); + +TEST_F(ValidateComposites, HalfMatrixCannotTranspose) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 0 RowMajor +OpMemberDecorate %block 0 MatrixStride 8 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 16 +%float2 = OpTypeVector %float 2 +%mat2x2 = OpTypeMatrix %float2 2 +%block = OpTypeStruct %mat2x2 +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%ptr_ssbo_mat2x2 = OpTypePointer StorageBuffer %mat2x2 +%var = OpVariable %ptr_ssbo_block StorageBuffer +%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +%gep = OpAccessChain %ptr_ssbo_mat2x2 %var %int_0 +%ld = OpLoad %mat2x2 %gep +%inst = OpTranspose %mat2x2 %ld +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cannot transpose matrices of 16-bit floats")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_constants_test.cpp b/3rdparty/spirv-tools/test/val/val_constants_test.cpp index 0c26d1439..72ce8dfdb 100644 --- a/3rdparty/spirv-tools/test/val/val_constants_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_constants_test.cpp @@ -22,14 +22,17 @@ #include "gmock/gmock.h" #include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" #include "test/val/val_fixtures.h" namespace spvtools { namespace val { namespace { +using ::testing::Combine; using ::testing::Eq; using ::testing::HasSubstr; +using ::testing::Values; using ::testing::ValuesIn; using ValidateConstant = spvtest::ValidateBase; @@ -367,6 +370,78 @@ OpMemoryModel Logical GLSL450 "requires Kernel capability or extension SPV_AMD_gpu_shader_int16")); } +using SmallStorageConstants = spvtest::ValidateBase; + +CodeGenerator GetSmallStorageCodeGenerator() { + CodeGenerator generator; + generator.capabilities_ = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpCapability StoragePushConstant16 +OpCapability StorageInputOutput16 +OpCapability UniformAndStorageBuffer8BitAccess +OpCapability StoragePushConstant8 +)"; + generator.extensions_ = R"( +OpExtension "SPV_KHR_16bit_storage" +OpExtension "SPV_KHR_8bit_storage" +)"; + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + generator.types_ = R"( +%short = OpTypeInt 16 0 +%short2 = OpTypeVector %short 2 +%char = OpTypeInt 8 0 +%char2 = OpTypeVector %char 2 +%half = OpTypeFloat 16 +%half2 = OpTypeVector %half 2 +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +)"; + return generator; +} + +TEST_P(SmallStorageConstants, SmallConstant) { + std::string constant = GetParam(); + CodeGenerator generator = GetSmallStorageCodeGenerator(); + generator.after_types_ += constant + "\n"; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Cannot form constants of 8- or 16-bit types")); +} + +// Constant composites would be caught through scalar constants. +INSTANTIATE_TEST_SUITE_P( + SmallConstants, SmallStorageConstants, + Values("%c = OpConstant %char 0", "%c = OpConstantNull %char2", + "%c = OpConstant %short 0", "%c = OpConstantNull %short", + "%c = OpConstant %half 0", "%c = OpConstantNull %half", + "%c = OpSpecConstant %char 0", "%c = OpSpecConstant %short 0", + "%c = OpSpecConstant %half 0", + "%c = OpSpecConstantOp %char SConvert %int_0", + "%c = OpSpecConstantOp %short SConvert %int_0", + "%c = OpSpecConstantOp %half FConvert %float_0")); + +TEST_F(ValidateConstant, NullPointerTo16BitStorageOk) { + std::string spirv = R"( +OpCapability Shader +OpCapability VariablePointersStorageBuffer +OpCapability UniformAndStorageBuffer16BitAccess +OpCapability Linkage +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +%half = OpTypeFloat 16 +%ptr_ssbo_half = OpTypePointer StorageBuffer %half +%null_ptr = OpConstantNull %ptr_ssbo_half +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_conversion_test.cpp b/3rdparty/spirv-tools/test/val/val_conversion_test.cpp index f905657bd..8851af3f2 100644 --- a/3rdparty/spirv-tools/test/val/val_conversion_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_conversion_test.cpp @@ -18,6 +18,7 @@ #include "gmock/gmock.h" #include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" #include "test/val/val_fixtures.h" namespace spvtools { @@ -26,6 +27,7 @@ namespace { using ::testing::HasSubstr; using ::testing::Not; +using ::testing::Values; using ValidateConversion = spvtest::ValidateBase; @@ -639,170 +641,6 @@ TEST_F(ValidateConversion, QuantizeToF16WrongInputType) { "Expected input type to be equal to Result Type: QuantizeToF16")); } -TEST_F(ValidateConversion, ConvertFToS8BitStorage) { - const std::string capabilities_and_extensions = R"( -OpCapability StorageBuffer8BitAccess -OpExtension "SPV_KHR_8bit_storage" -OpExtension "SPV_KHR_storage_buffer_storage_class" -)"; - - const std::string decorations = R"( -OpDecorate %ssbo Block -OpDecorate %ssbo Binding 0 -OpDecorate %ssbo DescriptorSet 0 -OpMemberDecorate %ssbo 0 Offset 0 -)"; - - const std::string types = R"( -%i8 = OpTypeInt 8 1 -%i8ptr = OpTypePointer StorageBuffer %i8 -%ssbo = OpTypeStruct %i8 -%ssboptr = OpTypePointer StorageBuffer %ssbo -)"; - - const std::string variables = R"( -%var = OpVariable %ssboptr StorageBuffer -)"; - - const std::string body = R"( -%val = OpConvertFToS %i8 %f32_2 -%accesschain = OpAccessChain %i8ptr %var %u32_0 -OpStore %accesschain %val -)"; - - CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions, - decorations, types, variables) - .c_str()); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "Invalid cast to 8-bit integer from a floating-point: ConvertFToS")); -} - -TEST_F(ValidateConversion, ConvertFToU8BitStorage) { - const std::string capabilities_and_extensions = R"( -OpCapability StorageBuffer8BitAccess -OpExtension "SPV_KHR_8bit_storage" -OpExtension "SPV_KHR_storage_buffer_storage_class" -)"; - - const std::string decorations = R"( -OpDecorate %ssbo Block -OpDecorate %ssbo Binding 0 -OpDecorate %ssbo DescriptorSet 0 -OpMemberDecorate %ssbo 0 Offset 0 -)"; - - const std::string types = R"( -%u8 = OpTypeInt 8 0 -%u8ptr = OpTypePointer StorageBuffer %u8 -%ssbo = OpTypeStruct %u8 -%ssboptr = OpTypePointer StorageBuffer %ssbo -)"; - - const std::string variables = R"( -%var = OpVariable %ssboptr StorageBuffer -)"; - - const std::string body = R"( -%val = OpConvertFToU %u8 %f32_2 -%accesschain = OpAccessChain %u8ptr %var %u32_0 -OpStore %accesschain %val -)"; - - CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions, - decorations, types, variables) - .c_str()); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "Invalid cast to 8-bit integer from a floating-point: ConvertFToU")); -} - -TEST_F(ValidateConversion, ConvertSToF8BitStorage) { - const std::string capabilities_and_extensions = R"( -OpCapability StorageBuffer8BitAccess -OpExtension "SPV_KHR_8bit_storage" -OpExtension "SPV_KHR_storage_buffer_storage_class" -)"; - - const std::string decorations = R"( -OpDecorate %ssbo Block -OpDecorate %ssbo Binding 0 -OpDecorate %ssbo DescriptorSet 0 -OpMemberDecorate %ssbo 0 Offset 0 -)"; - - const std::string types = R"( -%i8 = OpTypeInt 8 1 -%i8ptr = OpTypePointer StorageBuffer %i8 -%ssbo = OpTypeStruct %i8 -%ssboptr = OpTypePointer StorageBuffer %ssbo -)"; - - const std::string variables = R"( -%var = OpVariable %ssboptr StorageBuffer -)"; - - const std::string body = R"( -%accesschain = OpAccessChain %i8ptr %var %u32_0 -%load = OpLoad %i8 %accesschain -%val = OpConvertSToF %f32 %load -)"; - - CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions, - decorations, types, variables) - .c_str()); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "Invalid cast to floating-point from an 8-bit integer: ConvertSToF")); -} - -TEST_F(ValidateConversion, ConvertUToF8BitStorage) { - const std::string capabilities_and_extensions = R"( -OpCapability StorageBuffer8BitAccess -OpExtension "SPV_KHR_8bit_storage" -OpExtension "SPV_KHR_storage_buffer_storage_class" -)"; - - const std::string decorations = R"( -OpDecorate %ssbo Block -OpDecorate %ssbo Binding 0 -OpDecorate %ssbo DescriptorSet 0 -OpMemberDecorate %ssbo 0 Offset 0 -)"; - - const std::string types = R"( -%u8 = OpTypeInt 8 0 -%u8ptr = OpTypePointer StorageBuffer %u8 -%ssbo = OpTypeStruct %u8 -%ssboptr = OpTypePointer StorageBuffer %ssbo -)"; - - const std::string variables = R"( -%var = OpVariable %ssboptr StorageBuffer -)"; - - const std::string body = R"( -%accesschain = OpAccessChain %u8ptr %var %u32_0 -%load = OpLoad %u8 %accesschain -%val = OpConvertUToF %f32 %load -)"; - - CompileSuccessfully(GenerateShaderCode(body, capabilities_and_extensions, - decorations, types, variables) - .c_str()); - ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions()); - EXPECT_THAT( - getDiagnosticString(), - HasSubstr( - "Invalid cast to floating-point from an 8-bit integer: ConvertUToF")); -} - TEST_F(ValidateConversion, ConvertPtrToUSuccess) { const std::string body = R"( %ptr = OpVariable %f32ptr_func Function @@ -1580,6 +1418,148 @@ OpFunctionEnd "PhysicalStorageBufferEXT: ConvertPtrToU")); } +using ValidateSmallConversions = spvtest::ValidateBase; + +CodeGenerator GetSmallConversionsCodeGenerator() { + CodeGenerator generator; + generator.capabilities_ = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpCapability UniformAndStorageBuffer8BitAccess +)"; + generator.extensions_ = R"( +OpExtension "SPV_KHR_16bit_storage" +OpExtension "SPV_KHR_8bit_storage" +)"; + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + generator.before_types_ = R"( +OpDecorate %char_block Block +OpMemberDecorate %char_block 0 Offset 0 +OpDecorate %short_block Block +OpMemberDecorate %short_block 0 Offset 0 +OpDecorate %half_block Block +OpMemberDecorate %half_block 0 Offset 0 +OpDecorate %int_block Block +OpMemberDecorate %int_block 0 Offset 0 +OpDecorate %float_block Block +OpMemberDecorate %float_block 0 Offset 0 +)"; + generator.types_ = R"( +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int2 = OpTypeVector %int 2 +%float = OpTypeFloat 32 +%float_0 = OpConstant %float 0 +%float2 = OpTypeVector %float 2 +%char = OpTypeInt 8 0 +%char2 = OpTypeVector %char 2 +%short = OpTypeInt 16 0 +%short2 = OpTypeVector %short 2 +%half = OpTypeFloat 16 +%half2 = OpTypeVector %half 2 +%char_block = OpTypeStruct %char2 +%short_block = OpTypeStruct %short2 +%half_block = OpTypeStruct %half2 +%int_block = OpTypeStruct %int2 +%float_block = OpTypeStruct %float2 +%ptr_ssbo_char_block = OpTypePointer StorageBuffer %char_block +%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2 +%ptr_ssbo_char = OpTypePointer StorageBuffer %char +%ptr_ssbo_short_block = OpTypePointer StorageBuffer %short_block +%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2 +%ptr_ssbo_short = OpTypePointer StorageBuffer %short +%ptr_ssbo_half_block = OpTypePointer StorageBuffer %half_block +%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2 +%ptr_ssbo_half = OpTypePointer StorageBuffer %half +%ptr_ssbo_int_block = OpTypePointer StorageBuffer %int_block +%ptr_ssbo_int2 = OpTypePointer StorageBuffer %int2 +%ptr_ssbo_int = OpTypePointer StorageBuffer %int +%ptr_ssbo_float_block = OpTypePointer StorageBuffer %float_block +%ptr_ssbo_float2 = OpTypePointer StorageBuffer %float2 +%ptr_ssbo_float = OpTypePointer StorageBuffer %float +%void_fn = OpTypeFunction %void +%char_var = OpVariable %ptr_ssbo_char_block StorageBuffer +%short_var = OpVariable %ptr_ssbo_short_block StorageBuffer +%half_var = OpVariable %ptr_ssbo_half_block StorageBuffer +%int_var = OpVariable %ptr_ssbo_int_block StorageBuffer +%float_var = OpVariable %ptr_ssbo_float_block StorageBuffer +)"; + generator.after_types_ = R"( +%func = OpFunction %void None %void_fn +%entry = OpLabel +%char2_gep = OpAccessChain %ptr_ssbo_char2 %char_var %int_0 +%ld_char2 = OpLoad %char2 %char2_gep +%char_gep = OpAccessChain %ptr_ssbo_char %char_var %int_0 %int_0 +%ld_char = OpLoad %char %char_gep +%short2_gep = OpAccessChain %ptr_ssbo_short2 %short_var %int_0 +%ld_short2 = OpLoad %short2 %short2_gep +%short_gep = OpAccessChain %ptr_ssbo_short %short_var %int_0 %int_0 +%ld_short = OpLoad %short %short_gep +%half2_gep = OpAccessChain %ptr_ssbo_half2 %half_var %int_0 +%ld_half2 = OpLoad %half2 %half2_gep +%half_gep = OpAccessChain %ptr_ssbo_half %half_var %int_0 %int_0 +%ld_half = OpLoad %half %half_gep +%int2_gep = OpAccessChain %ptr_ssbo_int2 %int_var %int_0 +%ld_int2 = OpLoad %int2 %int2_gep +%int_gep = OpAccessChain %ptr_ssbo_int %int_var %int_0 %int_0 +%ld_int = OpLoad %int %int_gep +%float2_gep = OpAccessChain %ptr_ssbo_float2 %float_var %int_0 +%ld_float2 = OpLoad %float2 %float2_gep +%float_gep = OpAccessChain %ptr_ssbo_float %float_var %int_0 %int_0 +%ld_float = OpLoad %float %float_gep +)"; + generator.add_at_the_end_ = R"( +OpReturn +OpFunctionEnd +)"; + return generator; +} + +TEST_P(ValidateSmallConversions, Instruction) { + CodeGenerator generator = GetSmallConversionsCodeGenerator(); + generator.after_types_ += GetParam() + "\n"; + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_DATA, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "8- or 16-bit types can only be used with width-only conversions")); +} + +INSTANTIATE_TEST_SUITE_P(SmallConversionInstructions, ValidateSmallConversions, + Values("%inst = OpConvertFToU %char %ld_float", + "%inst = OpConvertFToU %char2 %ld_float2", + "%inst = OpConvertFToU %short %ld_float", + "%inst = OpConvertFToU %short2 %ld_float2", + "%inst = OpConvertFToU %int %ld_half", + "%inst = OpConvertFToU %int2 %ld_half2", + "%inst = OpConvertFToS %char %ld_float", + "%inst = OpConvertFToS %char2 %ld_float2", + "%inst = OpConvertFToS %short %ld_float", + "%inst = OpConvertFToS %short2 %ld_float2", + "%inst = OpConvertFToS %int %ld_half", + "%inst = OpConvertFToS %int2 %ld_half2", + "%inst = OpConvertSToF %float %ld_char", + "%inst = OpConvertSToF %float2 %ld_char2", + "%inst = OpConvertSToF %float %ld_short", + "%inst = OpConvertSToF %float2 %ld_short2", + "%inst = OpConvertSToF %half %ld_int", + "%inst = OpConvertSToF %half2 %ld_int2", + "%inst = OpConvertUToF %float %ld_char", + "%inst = OpConvertUToF %float2 %ld_char2", + "%inst = OpConvertUToF %float %ld_short", + "%inst = OpConvertUToF %float2 %ld_short2", + "%inst = OpConvertUToF %half %ld_int", + "%inst = OpConvertUToF %half2 %ld_int2", + "%inst = OpBitcast %half %ld_short", + "%inst = OpBitcast %half2 %ld_short2", + "%inst = OpBitcast %short %ld_half", + "%inst = OpBitcast %short2 %ld_half2")); + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_data_test.cpp b/3rdparty/spirv-tools/test/val/val_data_test.cpp index 7dd02697b..4690f97ac 100644 --- a/3rdparty/spirv-tools/test/val/val_data_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_data_test.cpp @@ -717,8 +717,7 @@ OpTypeForwardPointer %_ptr_Generic_struct_A Generic } TEST_F(ValidateData, ext_16bit_storage_caps_allow_free_fp_rounding_mode) { - for (const char* cap : {"StorageUniform16", "StorageUniformBufferBlock16", - "StoragePushConstant16", "StorageInputOutput16"}) { + for (const char* cap : {"StorageUniform16", "StorageUniformBufferBlock16"}) { for (const char* mode : {"RTE", "RTZ", "RTP", "RTN"}) { std::string str = std::string(R"( OpCapability Shader diff --git a/3rdparty/spirv-tools/test/val/val_decoration_test.cpp b/3rdparty/spirv-tools/test/val/val_decoration_test.cpp index 9ecb3828a..c267dbf59 100644 --- a/3rdparty/spirv-tools/test/val/val_decoration_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_decoration_test.cpp @@ -4554,41 +4554,6 @@ OpFunctionEnd "pointer to a 16-bit floating-point scalar or vector object.")); } -TEST_F(ValidateDecorations, FPRoundingModeBadStorageClass) { - std::string spirv = R"( -OpCapability Shader -OpCapability Linkage -OpCapability StorageBuffer16BitAccess -OpExtension "SPV_KHR_storage_buffer_storage_class" -OpExtension "SPV_KHR_variable_pointers" -OpExtension "SPV_KHR_16bit_storage" -OpMemoryModel Logical GLSL450 -OpEntryPoint GLCompute %main "main" -OpDecorate %_ FPRoundingMode RTE -%half = OpTypeFloat 16 -%float = OpTypeFloat 32 -%float_1_25 = OpConstant %float 1.25 -%half_ptr = OpTypePointer Private %half -%half_ptr_var = OpVariable %half_ptr Private -%void = OpTypeVoid -%func = OpTypeFunction %void -%main = OpFunction %void None %func -%main_entry = OpLabel -%_ = OpFConvert %half %float_1_25 -OpStore %half_ptr_var %_ -OpReturn -OpFunctionEnd - )"; - - CompileSuccessfully(spirv); - EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState()); - EXPECT_THAT(getDiagnosticString(), - HasSubstr("FPRoundingMode decoration can be applied only to the " - "Object operand of an OpStore in the StorageBuffer, " - "PhysicalStorageBufferEXT, Uniform, " - "PushConstant, Input, or Output Storage Classes.")); -} - TEST_F(ValidateDecorations, FPRoundingModeMultipleOpStoreGood) { std::string spirv = R"( OpCapability Shader diff --git a/3rdparty/spirv-tools/test/val/val_memory_test.cpp b/3rdparty/spirv-tools/test/val/val_memory_test.cpp index c28a11d56..54009dfb2 100644 --- a/3rdparty/spirv-tools/test/val/val_memory_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_memory_test.cpp @@ -19,12 +19,14 @@ #include "gmock/gmock.h" #include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" #include "test/val/val_fixtures.h" namespace spvtools { namespace val { namespace { +using ::testing::Combine; using ::testing::Eq; using ::testing::HasSubstr; using ::testing::Values; @@ -3832,6 +3834,465 @@ OpFunctionEnd HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks")); } +using ValidateSizedVariable = + spvtest::ValidateBase>; + +CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit) { + CodeGenerator generator; + generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n"; + generator.extensions_ = + "OpExtension \"SPV_KHR_16bit_storage\"\nOpExtension " + "\"SPV_KHR_8bit_storage\"\n"; + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + if (is_8bit) { + generator.before_types_ = R"(OpDecorate %char_buffer_block BufferBlock +OpMemberDecorate %char_buffer_block 0 Offset 0 +)"; + generator.types_ = R"(%void = OpTypeVoid +%char = OpTypeInt 8 0 +%char4 = OpTypeVector %char 4 +%char_buffer_block = OpTypeStruct %char +)"; + } else { + generator.before_types_ = R"(OpDecorate %half_buffer_block BufferBlock +OpDecorate %short_buffer_block BufferBlock +OpMemberDecorate %half_buffer_block 0 Offset 0 +OpMemberDecorate %short_buffer_block 0 Offset 0 +)"; + generator.types_ = R"(%void = OpTypeVoid +%short = OpTypeInt 16 0 +%half = OpTypeFloat 16 +%short4 = OpTypeVector %short 4 +%half4 = OpTypeVector %half 4 +%mat4x4 = OpTypeMatrix %half4 4 +%short_buffer_block = OpTypeStruct %short +%half_buffer_block = OpTypeStruct %half +)"; + } + generator.after_types_ = R"(%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +)"; + generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n"; + return generator; +} + +TEST_P(ValidateSizedVariable, Capability) { + const std::string storage_class = std::get<0>(GetParam()); + const std::string capability = std::get<1>(GetParam()); + const std::string var_type = std::get<2>(GetParam()); + + bool type_8bit = false; + if (var_type == "%char" || var_type == "%char4" || + var_type == "%char_buffer_block") { + type_8bit = true; + } + + auto generator = GetSizedVariableCodeGenerator(type_8bit); + generator.types_ += "%ptr_type = OpTypePointer " + storage_class + " " + + var_type + "\n%var = OpVariable %ptr_type " + + storage_class + "\n"; + generator.capabilities_ += "OpCapability " + capability + "\n"; + + bool capability_ok = false; + bool storage_class_ok = false; + if (storage_class == "Input" || storage_class == "Output") { + if (!type_8bit) { + capability_ok = capability == "StorageInputOutput16"; + storage_class_ok = true; + } + } else if (storage_class == "StorageBuffer") { + if (type_8bit) { + capability_ok = capability == "StorageBuffer8BitAccess" || + capability == "UniformAndStorageBuffer8BitAccess"; + } else { + capability_ok = capability == "StorageBuffer16BitAccess" || + capability == "UniformAndStorageBuffer16BitAccess"; + } + storage_class_ok = true; + } else if (storage_class == "PushConstant") { + if (type_8bit) { + capability_ok = capability == "StoragePushConstant8"; + } else { + capability_ok = capability == "StoragePushConstant16"; + } + storage_class_ok = true; + } else if (storage_class == "Uniform") { + bool buffer_block = var_type.find("buffer_block") != std::string::npos; + if (type_8bit) { + capability_ok = capability == "UniformAndStorageBuffer8BitAccess" || + (capability == "StorageBuffer8BitAccess" && buffer_block); + } else { + capability_ok = + capability == "UniformAndStorageBuffer16BitAccess" || + (capability == "StorageBuffer16BitAccess" && buffer_block); + } + storage_class_ok = true; + } + + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + spv_result_t result = ValidateInstructions(SPV_ENV_UNIVERSAL_1_3); + if (capability_ok) { + EXPECT_EQ(SPV_SUCCESS, result); + } else { + EXPECT_EQ(SPV_ERROR_INVALID_ID, result); + if (storage_class_ok) { + std::string message = std::string("Allocating a variable containing a ") + + (type_8bit ? "8" : "16") + "-bit element in " + + storage_class + + " storage class requires an additional capability"; + EXPECT_THAT(getDiagnosticString(), HasSubstr(message)); + } else { + std::string message = + std::string("Cannot allocate a variable containing a ") + + (type_8bit ? "8" : "16") + "-bit type in " + storage_class + + " storage class"; + EXPECT_THAT(getDiagnosticString(), HasSubstr(message)); + } + } +} + +INSTANTIATE_TEST_SUITE_P( + Storage8, ValidateSizedVariable, + Combine(Values("UniformConstant", "Input", "Output", "Workgroup", + "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"), + Values("StorageBuffer8BitAccess", + "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8"), + Values("%char", "%char4", "%char_buffer_block"))); + +INSTANTIATE_TEST_SUITE_P( + Storage16, ValidateSizedVariable, + Combine(Values("UniformConstant", "Input", "Output", "Workgroup", + "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"), + Values("StorageBuffer16BitAccess", + "UniformAndStorageBuffer16BitAccess", + "StoragePushConstant16", "StorageInputOutput16"), + Values("%short", "%half", "%short4", "%half4", "%mat4x4", + "%short_buffer_block", "%half_buffer_block"))); + +using ValidateSizedLoadStore = + spvtest::ValidateBase>; + +CodeGenerator GetSizedLoadStoreCodeGenerator(const std::string& base_type, + uint32_t width) { + CodeGenerator generator; + generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n"; + if (width == 8) { + generator.capabilities_ += + "OpCapability UniformAndStorageBuffer8BitAccess\n"; + generator.extensions_ = "OpExtension \"SPV_KHR_8bit_storage\"\n"; + } else { + generator.capabilities_ += + "OpCapability UniformAndStorageBuffer16BitAccess\n"; + generator.extensions_ = "OpExtension \"SPV_KHR_16bit_storage\"\n"; + } + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + generator.before_types_ = R"(OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %struct 0 Offset 0 +)"; + generator.types_ = R"(%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%int_1 = OpConstant %int 1 +%int_2 = OpConstant %int 2 +%int_3 = OpConstant %int 3 +)"; + + if (width == 8) { + generator.types_ += R"(%scalar = OpTypeInt 8 0 +%vector = OpTypeVector %scalar 4 +%struct = OpTypeStruct %vector +)"; + } else if (base_type == "int") { + generator.types_ += R"(%scalar = OpTypeInt 16 0 +%vector = OpTypeVector %scalar 4 +%struct = OpTypeStruct %vector +)"; + } else { + generator.types_ += R"(%scalar = OpTypeFloat 16 +%vector = OpTypeVector %scalar 4 +%matrix = OpTypeMatrix %vector 4 +%struct = OpTypeStruct %matrix +%ptr_ssbo_matrix = OpTypePointer StorageBuffer %matrix +)"; + generator.before_types_ += R"(OpMemberDecorate %struct 0 RowMajor +OpMemberDecorate %struct 0 MatrixStride 16 +)"; + } + generator.types_ += R"(%block = OpTypeStruct %struct +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct +%ptr_ssbo_vector = OpTypePointer StorageBuffer %vector +%ptr_ssbo_scalar = OpTypePointer StorageBuffer %scalar +%ld_var = OpVariable %ptr_ssbo_block StorageBuffer +%st_var = OpVariable %ptr_ssbo_block StorageBuffer +)"; + + generator.after_types_ = R"(%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +)"; + generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n"; + return generator; +} + +TEST_P(ValidateSizedLoadStore, Load) { + std::string base_type = std::get<0>(GetParam()); + uint32_t width = std::get<1>(GetParam()); + std::string mem_type = std::get<2>(GetParam()); + + CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width); + generator.after_types_ += + "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0"; + if (mem_type != "struct") { + generator.after_types_ += " %int_0"; + if (mem_type != "matrix" && base_type == "float") { + generator.after_types_ += " %int_0"; + } + if (mem_type == "scalar") { + generator.after_types_ += " %int_0"; + } + } + generator.after_types_ += "\n"; + generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n"; + + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + if (mem_type == "struct") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "8- or 16-bit loads must be a scalar, vector or matrix type")); + } else { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + } +} + +TEST_P(ValidateSizedLoadStore, Store) { + std::string base_type = std::get<0>(GetParam()); + uint32_t width = std::get<1>(GetParam()); + std::string mem_type = std::get<2>(GetParam()); + + CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width); + generator.after_types_ += + "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0"; + if (mem_type != "struct") { + generator.after_types_ += " %int_0"; + if (mem_type != "matrix" && base_type == "float") { + generator.after_types_ += " %int_0"; + } + if (mem_type == "scalar") { + generator.after_types_ += " %int_0"; + } + } + generator.after_types_ += "\n"; + generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n"; + generator.after_types_ += + "%st_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %st_var %int_0"; + if (mem_type != "struct") { + generator.after_types_ += " %int_0"; + if (mem_type != "matrix" && base_type == "float") { + generator.after_types_ += " %int_0"; + } + if (mem_type == "scalar") { + generator.after_types_ += " %int_0"; + } + } + generator.after_types_ += "\n"; + generator.after_types_ += "OpStore %st_gep %ld\n"; + + CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3); + if (mem_type == "struct") { + EXPECT_EQ(SPV_ERROR_INVALID_ID, + ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + // Can only catch the load. + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "8- or 16-bit loads must be a scalar, vector or matrix type")); + } else { + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + } +} + +INSTANTIATE_TEST_SUITE_P(LoadStoreInt8, ValidateSizedLoadStore, + Combine(Values("int"), Values(8u), + Values("scalar", "vector", "struct"))); +INSTANTIATE_TEST_SUITE_P(LoadStoreInt16, ValidateSizedLoadStore, + Combine(Values("int"), Values(16u), + Values("scalar", "vector", "struct"))); +INSTANTIATE_TEST_SUITE_P(LoadStoreFloat16, ValidateSizedLoadStore, + Combine(Values("float"), Values(16u), + Values("scalar", "vector", "matrix", + "struct"))); + +TEST_F(ValidateMemory, SmallStorageCopyMemoryChar) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer8BitAccess +OpExtension "SPV_KHR_8bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%char = OpTypeInt 8 0 +%block = OpTypeStruct %char +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%in = OpVariable %ptr_ssbo_block StorageBuffer +%out = OpVariable %ptr_ssbo_block StorageBuffer +%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %out %in +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types")); +} + +TEST_F(ValidateMemory, SmallStorageCopyMemoryShort) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%short = OpTypeInt 16 0 +%block = OpTypeStruct %short +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%in = OpVariable %ptr_ssbo_block StorageBuffer +%out = OpVariable %ptr_ssbo_block StorageBuffer +%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %out %in +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types")); +} + +TEST_F(ValidateMemory, SmallStorageCopyMemoryHalf) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability UniformAndStorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int_0 = OpConstant %int 0 +%half = OpTypeFloat 16 +%block = OpTypeStruct %half +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%in = OpVariable %ptr_ssbo_block StorageBuffer +%out = OpVariable %ptr_ssbo_block StorageBuffer +%void_fn = OpTypeFunction %void +%func = OpFunction %void None %void_fn +%entry = OpLabel +OpCopyMemory %out %in +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types")); +} + +TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockShort) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block BufferBlock +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%short = OpTypeInt 16 0 +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%block = OpTypeStruct %short +%block_array = OpTypeArray %block %int_4 +%ptr_block_array = OpTypePointer Uniform %block_array +%var = OpVariable %ptr_block_array Uniform +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockChar) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer8BitAccess +OpExtension "SPV_KHR_8bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block BufferBlock +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%char = OpTypeInt 8 0 +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%block = OpTypeStruct %char +%block_array = OpTypeArray %block %int_4 +%ptr_block_array = OpTypePointer Uniform %block_array +%var = OpVariable %ptr_block_array Uniform +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + +TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockHalf) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +OpDecorate %block BufferBlock +OpMemberDecorate %block 0 Offset 0 +%void = OpTypeVoid +%half = OpTypeFloat 16 +%int = OpTypeInt 32 0 +%int_4 = OpConstant %int 4 +%block = OpTypeStruct %half +%block_array = OpTypeArray %block %int_4 +%ptr_block_array = OpTypePointer Uniform %block_array +%var = OpVariable %ptr_block_array Uniform +)"; + + CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3)); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_misc_test.cpp b/3rdparty/spirv-tools/test/val/val_misc_test.cpp new file mode 100644 index 000000000..350f5616b --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_misc_test.cpp @@ -0,0 +1,88 @@ +// 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. + +// Validation tests for misc instructions + +#include +#include + +#include "gmock/gmock.h" +#include "test/unit_spirv.h" +#include "test/val/val_fixtures.h" + +namespace spvtools { +namespace val { +namespace { + +using ::testing::Eq; +using ::testing::HasSubstr; + +using ValidateMisc = spvtest::ValidateBase; + +TEST_F(ValidateMisc, UndefRestrictedShort) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +%short = OpTypeInt 16 0 +%undef = OpUndef %short +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot create undefined values with 8- or 16-bit types")); +} + +TEST_F(ValidateMisc, UndefRestrictedChar) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer8BitAccess +OpExtension "SPV_KHR_8bit_storage" +OpMemoryModel Logical GLSL450 +%char = OpTypeInt 8 0 +%undef = OpUndef %char +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot create undefined values with 8- or 16-bit types")); +} + +TEST_F(ValidateMisc, UndefRestrictedHalf) { + const std::string spirv = R"( +OpCapability Shader +OpCapability Linkage +OpCapability StorageBuffer16BitAccess +OpExtension "SPV_KHR_16bit_storage" +OpMemoryModel Logical GLSL450 +%half = OpTypeFloat 16 +%undef = OpUndef %half +)"; + + CompileSuccessfully(spirv); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("Cannot create undefined values with 8- or 16-bit types")); +} +} // namespace +} // namespace val +} // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_modes_test.cpp b/3rdparty/spirv-tools/test/val/val_modes_test.cpp index c5b1a378b..688f43399 100644 --- a/3rdparty/spirv-tools/test/val/val_modes_test.cpp +++ b/3rdparty/spirv-tools/test/val/val_modes_test.cpp @@ -1101,6 +1101,83 @@ OpFunctionEnd EXPECT_THAT(SPV_SUCCESS, ValidateInstructions()); } +TEST_F(ValidateMode, FragmentShaderDemoteVertexBad) { + const std::string spirv = R"( +OpCapability Shader +OpCapability DemoteToHelperInvocationEXT +OpExtension "SPV_EXT_demote_to_helper_invocation" +OpMemoryModel Logical GLSL450 +OpEntryPoint Vertex %main "main" +%bool = OpTypeBool +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpDemoteToHelperInvocationEXT +%1 = OpIsHelperInvocationEXT %bool +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr( + "OpDemoteToHelperInvocationEXT requires Fragment execution model")); + EXPECT_THAT( + getDiagnosticString(), + HasSubstr("OpIsHelperInvocationEXT requires Fragment execution model")); +} + +TEST_F(ValidateMode, FragmentShaderDemoteGood) { + const std::string spirv = R"( +OpCapability Shader +OpCapability DemoteToHelperInvocationEXT +OpExtension "SPV_EXT_demote_to_helper_invocation" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%bool = OpTypeBool +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpDemoteToHelperInvocationEXT +%1 = OpIsHelperInvocationEXT %bool +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_SUCCESS, ValidateInstructions()); +} + +TEST_F(ValidateMode, FragmentShaderDemoteBadType) { + const std::string spirv = R"( +OpCapability Shader +OpCapability DemoteToHelperInvocationEXT +OpExtension "SPV_EXT_demote_to_helper_invocation" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" +OpExecutionMode %main OriginUpperLeft +%u32 = OpTypeInt 32 0 +%void = OpTypeVoid +%void_fn = OpTypeFunction %void +%main = OpFunction %void None %void_fn +%entry = OpLabel +OpDemoteToHelperInvocationEXT +%1 = OpIsHelperInvocationEXT %u32 +OpReturn +OpFunctionEnd +)"; + + CompileSuccessfully(spirv); + EXPECT_THAT(SPV_ERROR_INVALID_DATA, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Expected bool scalar type as Result Type")); +} + } // namespace } // namespace val } // namespace spvtools diff --git a/3rdparty/spirv-tools/test/val/val_small_type_uses_test.cpp b/3rdparty/spirv-tools/test/val/val_small_type_uses_test.cpp new file mode 100644 index 000000000..b950af5b0 --- /dev/null +++ b/3rdparty/spirv-tools/test/val/val_small_type_uses_test.cpp @@ -0,0 +1,338 @@ +// 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. + +// Validation tests for 8- and 16-bit type uses. + +#include +#include + +#include "gmock/gmock.h" +#include "test/unit_spirv.h" +#include "test/val/val_code_generator.h" +#include "test/val/val_fixtures.h" + +namespace spvtools { +namespace val { +namespace { + +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Values; + +using ValidateSmallTypeUses = spvtest::ValidateBase; + +CodeGenerator GetSmallTypesGenerator() { + CodeGenerator generator; + generator.capabilities_ = R"( +OpCapability Shader +OpCapability StorageBuffer16BitAccess +OpCapability StorageBuffer8BitAccess +)"; + generator.extensions_ = R"( +OpExtension "SPV_KHR_16bit_storage" +OpExtension "SPV_KHR_8bit_storage" +OpExtension "SPV_KHR_storage_buffer_storage_class" +%ext = OpExtInstImport "GLSL.std.450" +)"; + generator.memory_model_ = "OpMemoryModel Logical GLSL450\n"; + std::string body = R"( +%short_gep = OpAccessChain %ptr_ssbo_short %var %int_0 %int_0 +%ld_short = OpLoad %short %short_gep +%short_to_int = OpSConvert %int %ld_short +%short_to_uint = OpUConvert %int %ld_short +%short_to_char = OpSConvert %char %ld_short +%short_to_uchar = OpSConvert %char %ld_short +%short2_gep = OpAccessChain %ptr_ssbo_short2 %var %int_0 +%ld_short2 = OpLoad %short2 %short2_gep +%short2_to_int2 = OpSConvert %int2 %ld_short2 +%short2_to_uint2 = OpUConvert %int2 %ld_short2 +%short2_to_char2 = OpSConvert %char2 %ld_short2 +%short2_to_uchar2 = OpSConvert %char2 %ld_short2 + +%char_gep = OpAccessChain %ptr_ssbo_char %var %int_2 %int_0 +%ld_char = OpLoad %char %char_gep +%char_to_int = OpSConvert %int %ld_char +%char_to_uint = OpUConvert %int %ld_char +%char_to_short = OpSConvert %short %ld_char +%char_to_ushort = OpSConvert %short %ld_char +%char2_gep = OpAccessChain %ptr_ssbo_char2 %var %int_2 +%ld_char2 = OpLoad %char2 %char2_gep +%char2_to_int2 = OpSConvert %int2 %ld_char2 +%char2_to_uint2 = OpUConvert %int2 %ld_char2 +%char2_to_short2 = OpSConvert %short2 %ld_char2 +%char2_to_ushort2 = OpSConvert %short2 %ld_char2 + +%half_gep = OpAccessChain %ptr_ssbo_half %var %int_1 %int_0 +%ld_half = OpLoad %half %half_gep +%half_to_float = OpFConvert %float %ld_half +%half2_gep = OpAccessChain %ptr_ssbo_half2 %var %int_1 +%ld_half2 = OpLoad %half2 %half2_gep +%half2_to_float2 = OpFConvert %float2 %ld_half2 + +%int_to_short = OpSConvert %short %int_0 +%int_to_ushort = OpUConvert %short %int_0 +%int_to_char = OpSConvert %char %int_0 +%int_to_uchar = OpUConvert %char %int_0 +%int2_to_short2 = OpSConvert %short2 %int2_0 +%int2_to_ushort2 = OpUConvert %short2 %int2_0 +%int2_to_char2 = OpSConvert %char2 %int2_0 +%int2_to_uchar2 = OpUConvert %char2 %int2_0 +%int_gep = OpAccessChain %ptr_ssbo_int %var %int_3 %int_0 +%int2_gep = OpAccessChain %ptr_ssbo_int2 %var %int_3 + +%float_to_half = OpFConvert %half %float_0 +%float2_to_half2 = OpFConvert %half2 %float2_0 +%float_gep = OpAccessChain %ptr_ssbo_float %var %int_4 %int_0 +%float2_gep = OpAccessChain %ptr_ssbo_float2 %var %int_4 +)"; + generator.entry_points_.push_back( + {"foo", "GLCompute", "OpExecutionMode %foo LocalSize 1 1 1", body, ""}); + generator.before_types_ = R"( +OpDecorate %block Block +OpMemberDecorate %block 0 Offset 0 +OpMemberDecorate %block 1 Offset 8 +OpMemberDecorate %block 2 Offset 16 +OpMemberDecorate %block 3 Offset 32 +OpMemberDecorate %block 4 Offset 64 +)"; + generator.types_ = R"( +%void = OpTypeVoid +%int = OpTypeInt 32 0 +%int2 = OpTypeVector %int 2 +%float = OpTypeFloat 32 +%float2 = OpTypeVector %float 2 +%bool = OpTypeBool +%bool2 = OpTypeVector %bool 2 +%char = OpTypeInt 8 0 +%char2 = OpTypeVector %char 2 +%ptr_ssbo_char = OpTypePointer StorageBuffer %char +%ptr_ssbo_char2 = OpTypePointer StorageBuffer %char2 +%short = OpTypeInt 16 0 +%short2 = OpTypeVector %short 2 +%ptr_ssbo_short = OpTypePointer StorageBuffer %short +%ptr_ssbo_short2 = OpTypePointer StorageBuffer %short2 +%half = OpTypeFloat 16 +%half2 = OpTypeVector %half 2 +%ptr_ssbo_half = OpTypePointer StorageBuffer %half +%ptr_ssbo_half2 = OpTypePointer StorageBuffer %half2 +%ptr_ssbo_int = OpTypePointer StorageBuffer %int +%ptr_ssbo_int2 = OpTypePointer StorageBuffer %int2 +%ptr_ssbo_float = OpTypePointer StorageBuffer %float +%ptr_ssbo_float2 = OpTypePointer StorageBuffer %float2 +%block = OpTypeStruct %short2 %half2 %char2 %int2 %float2 +%ptr_ssbo_block = OpTypePointer StorageBuffer %block +%func = OpTypeFunction %void +)"; + generator.after_types_ = R"( +%var = OpVariable %ptr_ssbo_block StorageBuffer +%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 +%int2_0 = OpConstantComposite %int2 %int_0 %int_0 +%float_0 = OpConstant %float 0 +%float2_0 = OpConstantComposite %float2 %float_0 %float_0 + +%short_func_ty = OpTypeFunction %void %short +%char_func_ty = OpTypeFunction %void %char +%half_func_ty = OpTypeFunction %void %half +)"; + generator.add_at_the_end_ = R"( +%short_func = OpFunction %void None %short_func_ty +%short_param = OpFunctionParameter %short +%short_func_entry = OpLabel +OpReturn +OpFunctionEnd +%char_func = OpFunction %void None %char_func_ty +%char_param = OpFunctionParameter %char +%char_func_entry = OpLabel +OpReturn +OpFunctionEnd +%half_func = OpFunction %void None %half_func_ty +%half_param = OpFunctionParameter %half +%half_func_entry = OpLabel +OpReturn +OpFunctionEnd +)"; + + return generator; +} + +TEST_F(ValidateSmallTypeUses, BadCharPhi) { + CodeGenerator generator = GetSmallTypesGenerator(); + generator.entry_points_[0].body += R"( +OpBranch %next_block +%next_block = OpLabel +%phi = OpPhi %char %ld_char %foo_entry +)"; + + CompileSuccessfully(generator.Build()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid use of 8- or 16-bit result")); +} + +TEST_F(ValidateSmallTypeUses, BadShortPhi) { + CodeGenerator generator = GetSmallTypesGenerator(); + generator.entry_points_[0].body += R"( +OpBranch %next_block +%next_block = OpLabel +%phi = OpPhi %short %ld_short %foo_entry +)"; + + CompileSuccessfully(generator.Build()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid use of 8- or 16-bit result")); +} + +TEST_F(ValidateSmallTypeUses, BadHalfPhi) { + CodeGenerator generator = GetSmallTypesGenerator(); + generator.entry_points_[0].body += R"( +OpBranch %next_block +%next_block = OpLabel +%phi = OpPhi %half %ld_half %foo_entry +)"; + + CompileSuccessfully(generator.Build()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid use of 8- or 16-bit result")); +} + +using ValidateGoodUses = spvtest::ValidateBase; + +TEST_P(ValidateGoodUses, Inst) { + const std::string inst = GetParam(); + CodeGenerator generator = GetSmallTypesGenerator(); + generator.entry_points_[0].body += inst + "\n"; + + CompileSuccessfully(generator.Build()); + EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()); +} + +INSTANTIATE_TEST_SUITE_P( + SmallTypeUsesValid, ValidateGoodUses, + Values( + "%inst = OpIAdd %int %short_to_int %int_0", + "%inst = OpIAdd %int %short_to_uint %int_0", + "%inst = OpIAdd %int2 %short2_to_int2 %int2_0", + "%inst = OpIAdd %int2 %short2_to_uint2 %int2_0", + "%inst = OpIAdd %int %char_to_int %int_0", + "%inst = OpIAdd %int %char_to_uint %int_0", + "%inst = OpIAdd %int2 %char2_to_int2 %int2_0", + "%inst = OpIAdd %int2 %char2_to_uint2 %int2_0", + "%inst = OpUConvert %int %ld_short", + "%inst = OpSConvert %int %ld_short", + "%inst = OpUConvert %char %ld_short", + "%inst = OpSConvert %char %ld_short", + "%inst = OpUConvert %int %ld_char", "%inst = OpSConvert %int %ld_char", + "%inst = OpUConvert %short %ld_char", + "%inst = OpSConvert %short %ld_char", + "%inst = OpUConvert %int2 %ld_short2", + "%inst = OpSConvert %int2 %ld_short2", + "%inst = OpUConvert %char2 %ld_short2", + "%inst = OpSConvert %char2 %ld_short2", + "%inst = OpUConvert %int2 %ld_char2", + "%inst = OpSConvert %int2 %ld_char2", + "%inst = OpUConvert %short2 %ld_char2", + "%inst = OpSConvert %short2 %ld_char2", + "OpStore %short_gep %int_to_short", "OpStore %short_gep %int_to_ushort", + "OpStore %short_gep %char_to_short", + "OpStore %short_gep %char_to_ushort", + "OpStore %short2_gep %int2_to_short2", + "OpStore %short2_gep %int2_to_ushort2", + "OpStore %short2_gep %char2_to_short2", + "OpStore %short2_gep %char2_to_ushort2", + "OpStore %char_gep %int_to_char", "OpStore %char_gep %int_to_uchar", + "OpStore %char_gep %short_to_char", "OpStore %char_gep %short_to_uchar", + "OpStore %char2_gep %int2_to_char2", + "OpStore %char2_gep %int2_to_uchar2", + "OpStore %char2_gep %short2_to_char2", + "OpStore %char2_gep %short2_to_uchar2", + "OpStore %int_gep %short_to_int", "OpStore %int_gep %short_to_uint", + "OpStore %int_gep %char_to_int", "OpStore %int2_gep %char2_to_uint2", + "OpStore %int2_gep %short2_to_int2", + "OpStore %int2_gep %short2_to_uint2", + "OpStore %int2_gep %char2_to_int2", "OpStore %int2_gep %char2_to_uint2", + "%inst = OpFAdd %float %half_to_float %float_0", + "%inst = OpFAdd %float2 %half2_to_float2 %float2_0", + "%inst = OpFConvert %float %ld_half", + "%inst = OpFConvert %float2 %ld_half2", + "OpStore %half_gep %float_to_half", "OpStore %half_gep %ld_half", + "OpStore %half2_gep %float2_to_half2", "OpStore %half2_gep %ld_half2", + "OpStore %float_gep %half_to_float", + "OpStore %float2_gep %half2_to_float2")); + +using ValidateBadUses = spvtest::ValidateBase; + +TEST_P(ValidateBadUses, Inst) { + const std::string inst = GetParam(); + CodeGenerator generator = GetSmallTypesGenerator(); + generator.entry_points_[0].body += inst + "\n"; + + CompileSuccessfully(generator.Build()); + EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); + EXPECT_THAT(getDiagnosticString(), + HasSubstr("Invalid use of 8- or 16-bit result")); +} + +// A smattering of unacceptable use cases. Far too vast to cover exhaustively. +INSTANTIATE_TEST_SUITE_P( + SmallTypeUsesInvalid, ValidateBadUses, + Values("%inst = OpIAdd %short %ld_short %ld_short", + "%inst = OpIAdd %short %char_to_short %char_to_short", + "%inst = OpIAdd %short %char_to_ushort %char_to_ushort", + "%inst = OpIAdd %short %int_to_short %int_to_short", + "%inst = OpIAdd %short %int_to_ushort %int_to_ushort", + "%inst = OpIAdd %short2 %ld_short2 %ld_short2", + "%inst = OpIAdd %short2 %char2_to_short2 %char2_to_short2", + "%inst = OpIAdd %short2 %char2_to_ushort2 %char2_to_ushort2", + "%inst = OpIAdd %short2 %int2_to_short2 %int2_to_short2", + "%inst = OpIAdd %short2 %int2_to_ushort2 %int2_to_ushort2", + "%inst = OpIEqual %bool %ld_short %ld_short", + "%inst = OpIEqual %bool %char_to_short %char_to_short", + "%inst = OpIEqual %bool %char_to_ushort %char_to_ushort", + "%inst = OpIEqual %bool %int_to_short %int_to_short", + "%inst = OpIEqual %bool %int_to_ushort %int_to_ushort", + "%inst = OpIEqual %bool2 %ld_short2 %ld_short2", + "%inst = OpIEqual %bool2 %char2_to_short2 %char2_to_short2", + "%inst = OpIEqual %bool2 %char2_to_ushort2 %char2_to_ushort2", + "%inst = OpIEqual %bool2 %int2_to_short2 %int2_to_short2", + "%inst = OpIEqual %bool2 %int2_to_ushort2 %int2_to_ushort2", + "%inst = OpFAdd %half %ld_half %ld_half", + "%inst = OpFAdd %half %float_to_half %float_to_half", + "%inst = OpFAdd %half2 %ld_half2 %ld_half2", + "%inst = OpFAdd %half2 %float2_to_half2 %float2_to_half2", + "%inst = OpFOrdGreaterThan %bool %ld_half %ld_half", + "%inst = OpFOrdGreaterThan %bool %float_to_half %float_to_half", + "%inst = OpFOrdGreaterThan %bool2 %ld_half2 %ld_half2", + "%inst = OpFOrdGreaterThan %bool2 %float2_to_half2 %float2_to_half2", + "%inst = OpFunctionCall %void %short_func %ld_short", + "%inst = OpFunctionCall %void %short_func %char_to_short", + "%inst = OpFunctionCall %void %short_func %char_to_ushort", + "%inst = OpFunctionCall %void %short_func %int_to_short", + "%inst = OpFunctionCall %void %short_func %int_to_ushort", + "%inst = OpFunctionCall %void %char_func %ld_char", + "%inst = OpFunctionCall %void %char_func %short_to_char", + "%inst = OpFunctionCall %void %char_func %short_to_uchar", + "%inst = OpFunctionCall %void %char_func %int_to_char", + "%inst = OpFunctionCall %void %char_func %int_to_uchar", + "%inst = OpFunctionCall %void %half_func %ld_half", + "%inst = OpFunctionCall %void %half_func %float_to_half")); + +} // namespace +} // namespace val +} // namespace spvtools diff --git a/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp b/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp index 8967b4aed..481942fa9 100644 --- a/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp +++ b/3rdparty/spirv-tools/tools/fuzz/fuzz.cpp @@ -17,11 +17,13 @@ #include #include #include +#include #include #include "source/fuzz/fuzzer.h" #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" #include "source/fuzz/replayer.h" +#include "source/fuzz/shrinker.h" #include "source/opt/build_module.h" #include "source/opt/ir_context.h" #include "source/opt/log.h" @@ -32,10 +34,30 @@ namespace { +// Check that the std::system function can actually be used. +bool CheckExecuteCommand() { + int res = std::system(nullptr); + return res != 0; +} + +// Execute a command using the shell. +// Returns true if and only if the command's exit status was 0. +bool ExecuteCommand(const std::string& command) { + errno = 0; + int status = std::system(command.c_str()); + assert(errno == 0 && "failed to execute command"); + // The result returned by 'system' is implementation-defined, but is + // usually the case that the returned value is 0 when the command's exit + // code was 0. We are assuming that here, and that's all we depend on. + return status == 0; +} + // Status and actions to perform after parsing command-line arguments. enum class FuzzActions { FUZZ, // Run the fuzzer to apply transformations in a randomized fashion. REPLAY, // Replay an existing sequence of transformations. + SHRINK, // Shrink an existing sequence of transformations with respect to an + // interestingness function. STOP // Do nothing. }; @@ -71,6 +93,17 @@ Options (in lexicographical order): --seed Unsigned 32-bit integer seed to control random number generation. + --shrink + File from which to read a sequence of transformations to shrink + (instead of fuzzing) + --shrinker-step-limit + Unsigned 32-bit integer specifying maximum number of steps the + shrinker will take before giving up. Ignored unless --shrink + is used. + --interestingness + Path to an interestingness function to guide shrinking: a script + that returns 0 if and only if a given binary is interesting. + Required if --shrink is provided; disallowed otherwise. --version Display fuzzer version information. @@ -99,6 +132,8 @@ bool EndsWithSpv(const std::string& filename) { FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, std::string* out_binary_file, std::string* replay_transformations_file, + std::string* interestingness_function_file, + std::string* shrink_transformations_file, spvtools::FuzzerOptions* fuzzer_options) { uint32_t positional_arg_index = 0; @@ -122,6 +157,13 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); *replay_transformations_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--interestingness=", + sizeof("--interestingness=") - 1)) { + const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); + *interestingness_function_file = std::string(split_flag.second); + } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) { + const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); + *shrink_transformations_file = std::string(split_flag.second); } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) { const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); char* end = nullptr; @@ -130,6 +172,15 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, static_cast(strtol(split_flag.second.c_str(), &end, 10)); assert(end != split_flag.second.c_str() && errno == 0); fuzzer_options->set_random_seed(seed); + } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=", + sizeof("--shrinker-step-limit=") - 1)) { + const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg); + char* end = nullptr; + errno = 0; + const auto step_limit = + static_cast(strtol(split_flag.second.c_str(), &end, 10)); + assert(end != split_flag.second.c_str() && errno == 0); + fuzzer_options->set_shrinker_step_limit(step_limit); } else if ('\0' == cur_arg[1]) { // We do not support fuzzing from standard input. We could support // this if there was a compelling use case. @@ -173,12 +224,59 @@ FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file, if (!replay_transformations_file->empty()) { // A replay transformations file was given, thus the tool is being invoked // in replay mode. + if (!shrink_transformations_file->empty()) { + spvtools::Error( + FuzzDiagnostic, nullptr, {}, + "The --replay and --shrink arguments are mutually exclusive."); + return {FuzzActions::STOP, 1}; + } + if (!interestingness_function_file->empty()) { + spvtools::Error(FuzzDiagnostic, nullptr, {}, + "The --replay and --interestingness arguments are " + "mutually exclusive."); + return {FuzzActions::STOP, 1}; + } return {FuzzActions::REPLAY, 0}; } + if (!shrink_transformations_file->empty() ^ + !interestingness_function_file->empty()) { + spvtools::Error(FuzzDiagnostic, nullptr, {}, + "Both or neither of the --shrink and --interestingness " + "arguments must be provided."); + return {FuzzActions::STOP, 1}; + } + + if (!shrink_transformations_file->empty()) { + // The tool is being invoked in shrink mode. + assert(!interestingness_function_file->empty() && + "An error should have been raised if --shrink but not --interesting " + "was provided."); + return {FuzzActions::SHRINK, 0}; + } + return {FuzzActions::FUZZ, 0}; } +bool ParseTransformations( + const std::string& transformations_file, + spvtools::fuzz::protobufs::TransformationSequence* transformations) { + std::ifstream transformations_stream; + transformations_stream.open(transformations_file, + std::ios::in | std::ios::binary); + auto parse_success = + transformations->ParseFromIstream(&transformations_stream); + transformations_stream.close(); + if (!parse_success) { + spvtools::Error(FuzzDiagnostic, nullptr, {}, + ("Error reading transformations from file '" + + transformations_file + "'") + .c_str()); + return false; + } + return true; +} + bool Replay(const spv_target_env& target_env, const std::vector& binary_in, const spvtools::fuzz::protobufs::FactSequence& initial_facts, @@ -186,29 +284,61 @@ bool Replay(const spv_target_env& target_env, std::vector* binary_out, spvtools::fuzz::protobufs::TransformationSequence* transformations_applied) { - std::ifstream existing_transformations_file; - existing_transformations_file.open(replay_transformations_file, - std::ios::in | std::ios::binary); - spvtools::fuzz::protobufs::TransformationSequence - existing_transformation_sequence; - auto parse_success = existing_transformation_sequence.ParseFromIstream( - &existing_transformations_file); - existing_transformations_file.close(); - if (!parse_success) { - spvtools::Error(FuzzDiagnostic, nullptr, {}, - "Error reading transformations for replay"); + spvtools::fuzz::protobufs::TransformationSequence transformation_sequence; + if (!ParseTransformations(replay_transformations_file, + &transformation_sequence)) { return false; } spvtools::fuzz::Replayer replayer(target_env); replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); auto replay_result_status = - replayer.Run(binary_in, initial_facts, existing_transformation_sequence, + replayer.Run(binary_in, initial_facts, transformation_sequence, binary_out, transformations_applied); - if (replay_result_status != - spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete) { + return !(replay_result_status != + spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete); +} + +bool Shrink(const spv_target_env& target_env, + spv_const_fuzzer_options fuzzer_options, + const std::vector& binary_in, + const spvtools::fuzz::protobufs::FactSequence& initial_facts, + const std::string& shrink_transformations_file, + const std::string& interestingness_function_file, + std::vector* binary_out, + spvtools::fuzz::protobufs::TransformationSequence* + transformations_applied) { + spvtools::fuzz::protobufs::TransformationSequence transformation_sequence; + if (!ParseTransformations(shrink_transformations_file, + &transformation_sequence)) { return false; } - return true; + spvtools::fuzz::Shrinker shrinker(target_env, + fuzzer_options->shrinker_step_limit); + shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); + + spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function = + [interestingness_function_file](std::vector binary, + uint32_t reductions_applied) -> bool { + std::stringstream ss; + ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied + << ".spv"; + const auto spv_file = ss.str(); + const std::string command = + std::string(interestingness_function_file) + " " + spv_file; + auto write_file_succeeded = + WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size()); + (void)(write_file_succeeded); + assert(write_file_succeeded); + return ExecuteCommand(command); + }; + + auto shrink_result_status = shrinker.Run( + binary_in, initial_facts, transformation_sequence, + interestingness_function, binary_out, transformations_applied); + return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete == + shrink_result_status || + spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached == + shrink_result_status; } bool Fuzz(const spv_target_env& target_env, @@ -220,8 +350,8 @@ bool Fuzz(const spv_target_env& target_env, transformations_applied) { spvtools::fuzz::Fuzzer fuzzer(target_env); fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer); - auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, binary_out, - transformations_applied, fuzzer_options); + auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options, + binary_out, transformations_applied); if (fuzz_result_status != spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) { spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer"); @@ -238,11 +368,15 @@ int main(int argc, const char** argv) { std::string in_binary_file; std::string out_binary_file; std::string replay_transformations_file; + std::string interestingness_function_file; + std::string shrink_transformations_file; spvtools::FuzzerOptions fuzzer_options; - FuzzStatus status = ParseFlags(argc, argv, &in_binary_file, &out_binary_file, - &replay_transformations_file, &fuzzer_options); + FuzzStatus status = + ParseFlags(argc, argv, &in_binary_file, &out_binary_file, + &replay_transformations_file, &interestingness_function_file, + &shrink_transformations_file, &fuzzer_options); if (status.action == FuzzActions::STOP) { return status.code; @@ -290,6 +424,18 @@ int main(int argc, const char** argv) { return 1; } break; + case FuzzActions::SHRINK: { + if (!CheckExecuteCommand()) { + std::cerr << "could not find shell interpreter for executing a command" + << std::endl; + return 1; + } + if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts, + shrink_transformations_file, interestingness_function_file, + &binary_out, &transformations_applied)) { + return 1; + } + } break; default: assert(false && "Unknown fuzzer action."); break; diff --git a/3rdparty/spirv-tools/tools/opt/opt.cpp b/3rdparty/spirv-tools/tools/opt/opt.cpp index af527e33e..d29b8a0a6 100644 --- a/3rdparty/spirv-tools/tools/opt/opt.cpp +++ b/3rdparty/spirv-tools/tools/opt/opt.cpp @@ -357,6 +357,14 @@ Options (in lexicographical order):)", --merge-blocks followed by all the transformations implied by -O.)"); printf(R"( + --preserve-bindings + Ensure that the optimizer preserves all bindings declared within + the module, even when those bindings are unused.)"); + printf(R"( + --preserve-spec-constants + Ensure that the optimizer preserves all specialization constants declared + within the module, even when those constants are unused.)"); + printf(R"( --print-all Print SPIR-V assembly to standard error output before each pass and after the last pass.)"); @@ -693,6 +701,10 @@ OptStatus ParseFlags(int argc, const char** argv, optimizer_options->set_run_validator(false); } else if (0 == strcmp(cur_arg, "--print-all")) { optimizer->SetPrintAll(&std::cerr); + } else if (0 == strcmp(cur_arg, "--preserve-bindings")) { + optimizer_options->set_preserve_bindings(true); + } else if (0 == strcmp(cur_arg, "--preserve-spec-constants")) { + optimizer_options->set_preserve_spec_constants(true); } else if (0 == strcmp(cur_arg, "--time-report")) { optimizer->SetTimeReport(&std::cerr); } else if (0 == strcmp(cur_arg, "--relax-struct-store")) { diff --git a/scripts/shaderc.lua b/scripts/shaderc.lua index 495d10213..07953b086 100644 --- a/scripts/shaderc.lua +++ b/scripts/shaderc.lua @@ -129,6 +129,7 @@ project "spirv-opt" path.join(SPIRV_TOOLS, "source/val/validate_non_uniform.cpp"), path.join(SPIRV_TOOLS, "source/val/validate_primitives.cpp"), path.join(SPIRV_TOOLS, "source/val/validate_scopes.cpp"), + path.join(SPIRV_TOOLS, "source/val/validate_small_type_uses.cpp"), path.join(SPIRV_TOOLS, "source/val/validate_type.cpp"), path.join(SPIRV_TOOLS, "source/val/validation_state.cpp"), }